mysql_binlog 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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