File Coverage

blib/lib/MySQL/Binlog_RBR_Data.pm
Criterion Covered Total %
statement 68 77 88.3
branch 41 52 78.8
condition 15 30 50.0
subroutine 6 6 100.0
pod 1 1 100.0
total 131 166 78.9


line stmt bran cond sub pod time code
1             package MySQL::Binlog_RBR_Data;
2              
3 1     1   32497 use 5.008005;
  1         5  
  1         49  
4              
5             # be as strict and verbose as possible
6 1     1   6 use warnings;
  1         2  
  1         26  
7 1     1   4 use strict;
  1         12  
  1         109  
8              
9             # set version info
10             our $VERSION = "1.02";
11              
12 1     1   6 use Carp qw( confess croak );
  1         2  
  1         1818  
13              
14             sub parse {
15 2     2 1 741 my ( $class, $handle, $start_position, @interesting_tables ) = @_;
16              
17 2         4 my %interesting_tables;
18 2         7 @interesting_tables{ @interesting_tables } = @_;
19              
20 2         4 my $pos;
21             my @transactions;
22 0         0 my $trans;
23              
24             # mysqlbinlog outputs the binlog data between two lines with:
25             # DELIMITER
26             # so start working after the first one
27 2         352 while ( <$handle> ) {
28 188 50       478 if ( substr( $_, 0, 5 ) eq '# at ' ) {
29 0         0 $pos = 0 + substr( $_, 5 );
30 0         0 next;
31             }
32              
33             last
34 188 100       1418 if substr( $_, 0, 9 ) eq 'DELIMITER';
35             }
36              
37             # it's an error if we didn't get the DELIMITER, probably
38             # not a binlog file or a corrupted one
39 2 50 33     28 $_ && substr( $_, 0, 9 ) eq 'DELIMITER'
40             or croak "not a binlog file, didn't find DELIMITER";
41              
42             # want to start at some position? mysqlbinlog seeks on it, but we
43             # must parse the full output until we get at that position
44 2 100       8 if ( $start_position ) {
45 1         4 while ( <$handle> ) {
46 43 100       68 if ( substr( $_, 0, 5 ) eq '# at ' ) {
47 8         11 $pos = 0 + substr( $_, 5 );
48              
49 8 100       15 last if $pos >= $start_position;
50             }
51              
52             # got to EOF?
53             last
54 42 50       98 if substr( $_, 0, 9 ) eq 'DELIMITER';
55             }
56              
57 1 50       8 if ( $pos < $start_position ) {
    50          
58             # no guarantee $. points to 'at'
59 0         0 croak "position $start_position not found in binlog, last one found was: $pos";
60             }
61             elsif ( $pos > $start_position ) {
62 0         0 croak "position $start_position not found in binlog, next one after was: $pos, at $.";
63             }
64             }
65              
66 2         5 my ( $new_row, $old_row, $row );
67 0         0 my $end_pos;
68              
69             return sub {
70 9     9   24924 while ( <$handle> ) {
71             # position update
72 55 100       300 if ( substr( $_, 0, 5 ) eq '# at ' ) {
73 11         26 $pos = 0 + substr( $_, 5 );
74 11         32 next;
75             }
76              
77             # EOF?
78             last
79 44 100       96 if substr( $_, 0, 9 ) eq 'DELIMITER';
80              
81             # new transaction?
82             # transactions have a BEGIN and a COMMIT
83 42 100       120 if ( substr( $_, 0, 5 ) eq 'BEGIN' ) {
84 7         8 my %transaction;
85              
86 7         19 while ( <$handle> ) {
87             # EOT?
88             last
89 132 100       286 if substr( $_, 0, 6 ) eq 'COMMIT';
90              
91             # end of current record indicated by
92             # # end_log_pos
93 125 100 66     762 if ( substr( $_, 0, 2 ) eq '#1' # FIXME in 2020
94             && /^#\d.*end_log_pos (\d+)/ ) {
95 25         43 $end_pos = $1;
96 25         97 next;
97             }
98              
99             # not RBR? not interested
100 100 100       238 substr( $_, 0, 4 ) eq '### '
101             or next;
102              
103             # RBR statements format:
104              
105             # INSERT INTO ...
106             # SET
107             # values...
108              
109             # UPDATE ...
110             # WHERE
111             # old values...
112             # SET
113             # new values...
114              
115             # DELETE FROM ...
116             # WHERE
117             # old values...
118              
119             # so... SET defines new values
120             # WHERE defines old values
121              
122 68 100 100     1653 if ( $old_row && substr( $_, 4, 5 ) eq 'WHERE' ) {
    100 100        
    100 33        
    100          
    100          
    50          
123 6         16 $row = $old_row;
124             }
125             elsif ( $new_row && substr ( $_, 4, 3 ) eq 'SET' ) {
126 12         33 $row = $new_row;
127             }
128             elsif ( /^### INSERT INTO (\S+)/ ) {
129 8         13 $old_row = $row = undef;
130              
131 8 50 33     24 if ( ! @interesting_tables
132             || exists $interesting_tables{ $1 } ) {
133 8         12 $new_row = [];
134 8         11 push @{ $transaction{ $1 } }, [ $new_row ];
  8         42  
135             }
136             else {
137 0         0 $new_row = undef;
138             }
139             }
140             elsif ( /^### DELETE FROM (\S+)/ ) {
141 2         3 $new_row = $row = undef;
142              
143 2 50 33     14 if ( ! @interesting_tables
144             || exists $interesting_tables{ $1 } ) {
145 2         5 $old_row = [];
146 2         3 push @{ $transaction{ $1 } }, [ undef, $old_row ];
  2         21  
147             }
148             else {
149 0         0 $old_row = undef;
150             }
151             }
152             elsif ( /^### UPDATE (\S+)/ ) {
153 4         6 $row = undef;
154              
155 4 50 33     20 if ( ! @interesting_tables
156             || exists $interesting_tables{ $1 } ) {
157 4         5 $new_row = [];
158 4         9 $old_row = [];
159 4         6 push @{ $transaction{ $1 } }, [ $new_row, $old_row ];
  4         24  
160             }
161             else {
162 0         0 $new_row = $old_row = undef;
163             }
164             }
165             elsif ( $row && /@(\d+)=(.+)/ ) {
166 36         230 $row->[ $1 - 1 ] = $2;
167             }
168             }
169              
170             # did actually get EOT?
171 7 50 33     34 $_ && substr( $_, 0, 6 ) eq 'COMMIT'
172             or croak "truncated binlog file, at " . $handle->input_line_number() . ": $_";
173              
174             # last 'end_log_pos' seen
175 7         18 $transaction{ end_position } = $end_pos;
176             # first 'at' seen
177 7         12 $transaction{ start_position } = $pos;
178              
179 7         45 return \%transaction;
180             }
181             }
182              
183             # actually got EOF?
184 2 50 33     24 $_ && substr( $_, 0, 9 ) eq 'DELIMITER'
185             or croak "truncated binlog file, at " . $handle->input_line_number() . ": $_";
186              
187 2         14 return;
188 2         20 };
189             }
190              
191             1;
192              
193             __END__