mysql_binlog 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,11 +4,11 @@ module MysqlBinlog
4
4
  QUERY_EVENT_STATUS_TYPES = [
5
5
  :flags2, # 0
6
6
  :sql_mode, # 1
7
- :catalog, # 2
7
+ :catalog_deprecated, # 2
8
8
  :auto_increment, # 3
9
9
  :charset, # 4
10
10
  :time_zone, # 5
11
- :catalog_nz, # 6
11
+ :catalog, # 6
12
12
  :lc_time_names, # 7
13
13
  :charset_database, # 8
14
14
  :table_map_for_update, # 9
@@ -58,7 +58,7 @@ module MysqlBinlog
58
58
  parser.read_uint32_bitmap_by_name(QUERY_EVENT_FLAGS2)
59
59
  when :sql_mode
60
60
  parser.read_uint64
61
- when :catalog
61
+ when :catalog_deprecated
62
62
  parser.read_lpstringz
63
63
  when :auto_increment
64
64
  {
@@ -73,7 +73,7 @@ module MysqlBinlog
73
73
  }
74
74
  when :time_zone
75
75
  parser.read_lpstring
76
- when :catalog_nz
76
+ when :catalog
77
77
  parser.read_lpstring
78
78
  when :lc_time_names
79
79
  parser.read_uint16
@@ -107,6 +107,13 @@ module MysqlBinlog
107
107
  end
108
108
  end
109
109
 
110
+ def _table_map_event_metadata(columns_type)
111
+ length = parser.read_varint
112
+ columns_type.map do |c|
113
+ parser.read_mysql_type_metadata(c)
114
+ end
115
+ end
116
+
110
117
  # Parse additional fields for a +Table_map+ event.
111
118
  def table_map_event(header, fields)
112
119
  fields[:table_id] = parser.read_uint48
@@ -116,13 +123,14 @@ module MysqlBinlog
116
123
  map_entry[:table] = parser.read_lpstringz
117
124
  columns = parser.read_varint
118
125
  columns_type = parser.read_uint8_array(columns).map { |c| MYSQL_TYPES[c] }
119
- columns_metadata = parser.read_lpstring
126
+ columns_metadata = _table_map_event_metadata(columns_type)
120
127
  columns_nullable = parser.read_bit_array(columns)
121
128
 
122
129
  map_entry[:columns] = columns.times.map do |c|
123
130
  {
124
131
  :type => columns_type[c],
125
132
  :nullable => columns_nullable[c],
133
+ :metadata => columns_metadata[c],
126
134
  }
127
135
  end
128
136
 
@@ -132,22 +140,26 @@ module MysqlBinlog
132
140
  # Parse the row images present in a row-based replication row event. This
133
141
  # is rather incomplete right now due missing support for many MySQL types,
134
142
  # but can parse some basic events.
135
- def _generic_rows_event_row_images(header, fields)
143
+ def _generic_rows_event_row_images(header, fields, columns_used)
144
+ row_image_index = 0
136
145
  row_images = []
137
146
  end_position = reader.position + reader.remaining(header)
138
147
  while reader.position < end_position
139
148
  row_image = []
140
149
  columns_null = parser.read_bit_array(fields[:table][:columns].size)
141
150
  fields[:table][:columns].each_with_index do |column, column_index|
142
- if !fields[:columns_used][column_index]
151
+ if !columns_used[row_image_index][column_index]
143
152
  row_image << nil
144
153
  elsif columns_null[column_index]
145
154
  row_image << { column => nil }
146
155
  else
147
- row_image << { column => parser.read_mysql_type(column[:type]) }
156
+ row_image << {
157
+ column => parser.read_mysql_type(column[:type], column[:metadata])
158
+ }
148
159
  end
149
160
  end
150
161
  row_images << row_image
162
+ row_image_index += 1
151
163
  end
152
164
  row_images
153
165
  end
@@ -161,12 +173,12 @@ module MysqlBinlog
161
173
  fields[:table] = @table_map[table_id]
162
174
  fields[:flags] = parser.read_uint16
163
175
  columns = parser.read_varint
164
- fields[:columns_used] = parser.read_bit_array(columns)
176
+ columns_used = []
177
+ columns_used[0] = parser.read_bit_array(columns)
165
178
  if EVENT_TYPES[header[:event_type]] == :update_rows_event
166
- fields[:columns_update] = parser.read_bit_array(columns)
179
+ columns_used[1] = parser.read_bit_array(columns)
167
180
  end
168
- fields[:row_image] = _generic_rows_event_row_images(header, fields)
169
- fields.delete :columns_used
181
+ fields[:row_image] = _generic_rows_event_row_images(header, fields, columns_used)
170
182
  end
171
183
  alias :write_rows_event :generic_rows_event
172
184
  alias :update_rows_event :generic_rows_event
@@ -88,13 +88,31 @@ module MysqlBinlog
88
88
  reader.read(8).unpack("G").first
89
89
  end
90
90
 
91
- # Read a variable-length encoded integer. This is very broken at the
92
- # moment, and is just mapping to read_uint8, so it cannot handle numbers
93
- # greater than 251. This works fine for most structural elements of binary
94
- # logs, but will fall over with decoding actual RBR row images.
91
+ # Read a variable-length "Length Coded Binary" integer. This is derived
92
+ # from the MySQL protocol, and re-used in the binary log format. This
93
+ # format uses the first byte to alternately store the actual value for
94
+ # integer values <= 250, or to encode the number of following bytes
95
+ # used to store the actual value, which can be 2, 3, or 8. It also
96
+ # includes support for SQL NULL as a special case.
97
+ #
98
+ # See: http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol#Elements
95
99
  def read_varint
96
- # Cheating for now.
97
- read_uint8
100
+ first_byte = read_uint8
101
+
102
+ case
103
+ when first_byte <= 250
104
+ first_byte
105
+ when first_byte == 251
106
+ nil
107
+ when first_byte == 252
108
+ read_uint16
109
+ when first_byte == 253
110
+ read_uint24
111
+ when first_byte == 254
112
+ read_uint64
113
+ when first_byte == 255
114
+ raise "Invalid variable-length integer"
115
+ end
98
116
  end
99
117
 
100
118
  # Read a non-terminated string, provided its length.
@@ -110,17 +128,36 @@ module MysqlBinlog
110
128
  end
111
129
 
112
130
  # Read a (Pascal-style) length-prefixed string. The length is stored as a
113
- # 8-bit (1-byte) unsigned integer followed by the string itself with no
114
- # termination character.
115
- def read_lpstring
116
- length = read_uint8
131
+ # 8-bit (1-byte) to 32-bit (4-byte) unsigned integer, depending on the
132
+ # optional size parameter (default 1), followed by the string itself with
133
+ # no termination character.
134
+ def read_lpstring(size=1)
135
+ case size
136
+ when 1
137
+ length = read_uint8
138
+ when 2
139
+ length = read_uint16
140
+ when 3
141
+ length = read_uint24
142
+ when 4
143
+ length = read_uint32
144
+ end
117
145
  read_nstring(length)
118
146
  end
119
147
 
120
- # Read an lpstring which is also terminated with a null byte.
121
- def read_lpstringz
122
- length = read_uint8
123
- read_nstringz(length)
148
+ # Read an lpstring (as above) which is also terminated with a null byte.
149
+ def read_lpstringz(size=1)
150
+ string = read_lpstring(size)
151
+ reader.read(1) # null
152
+ string
153
+ end
154
+
155
+ # Read a MySQL-style varint length-prefixed string. The length is stored
156
+ # as a variable-length "Length Coded Binary" value (see read_varint) which
157
+ # is followed by the string content itself. No termination is included.
158
+ def read_varstring
159
+ length = read_varint
160
+ read_nstring(length)
124
161
  end
125
162
 
126
163
  # Read an array of unsigned 8-bit (1-byte) integers.
@@ -135,6 +172,8 @@ module MysqlBinlog
135
172
  data.unpack("b*").first.bytes.to_a.map { |i| (i-48) == 1 }.shift(length)
136
173
  end
137
174
 
175
+ # Read a uint32 value, and convert it to an array of symbols derived
176
+ # from a mapping table provided.
138
177
  def read_uint32_bitmap_by_name(names)
139
178
  value = read_uint32
140
179
  names.inject([]) do |result, (name, bit_value)|
@@ -164,11 +203,35 @@ module MysqlBinlog
164
203
  fields
165
204
  end
166
205
 
206
+ def read_mysql_type_metadata(column_type)
207
+ case column_type
208
+ when :float, :double
209
+ { :size => read_uint8 }
210
+ when :varchar
211
+ { :max_length => read_uint16 }
212
+ when :bit
213
+ { :size => read_uint8 }
214
+ when :decimal
215
+ {
216
+ :precision => read_uint8,
217
+ :decimals => read_uint8,
218
+ }
219
+ when :blob
220
+ { :length_size => read_uint8 }
221
+ when :var_string, :string
222
+ {
223
+ :real_type => read_uint8,
224
+ :length_size => read_uint8,
225
+ }
226
+ when :geometry
227
+ { :length_size => read_uint8 }
228
+ end
229
+ end
230
+
167
231
  # Read a single field, provided the MySQL column type as a symbol. Not all
168
232
  # types are currently supported.
169
- def read_mysql_type(column_type)
170
- case column_type
171
- #when :decimal
233
+ def read_mysql_type(type, metadata=nil)
234
+ case type
172
235
  when :tiny
173
236
  read_uint8
174
237
  when :short
@@ -179,33 +242,27 @@ module MysqlBinlog
179
242
  read_uint32
180
243
  when :longlong
181
244
  read_uint64
182
- when :string
183
- length = read_varint
184
- read_nstring(length)
185
-
186
245
  when :float
187
246
  read_float
188
247
  when :double
189
248
  read_double
190
- #when :null
249
+ when :string, :var_string
250
+ read_varstring
251
+ when :varchar, :blob
252
+ read_lpstring(metadata[:length_size])
191
253
  when :timestamp
192
254
  read_uint32
255
+ when :year
256
+ read_uint8 + 1900
193
257
  #when :date
194
258
  #when :time
195
259
  #when :datetime
196
- #when :year
197
260
  #when :newdate
198
- #when :varchar
199
261
  #when :bit
262
+ #when :decimal
200
263
  #when :newdecimal
201
264
  #when :enum
202
265
  #when :set
203
- #when :tiny_blob
204
- #when :medium_blob
205
- #when :long_blob
206
- #when :blob
207
- #when :var_string
208
- #when :string
209
266
  #when :geometry
210
267
  end
211
268
  end
@@ -52,6 +52,7 @@ module MysqlBinlog
52
52
  def read(length)
53
53
  return "" if length == 0
54
54
  data = @binlog.read(length)
55
+ puts_hex data
55
56
  if !data
56
57
  raise MalformedBinlogException.new
57
58
  elsif data.length == 0
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql_binlog
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Jeremy Cole