innodb_ruby 0.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,31 +3,187 @@
3
3
  require "pp"
4
4
  require "innodb"
5
5
 
6
- file, page_number = ARGV.shift(2)
6
+ class EdgesRecordFormatter
7
+ def format(page)
8
+ bytes_per_record = (page.record_space / page.page_header[:n_recs])
9
+ case
10
+ when [48, 26].include?(bytes_per_record) # Clustered Key
11
+ {
12
+ # PRIMARY KEY (source_id, state, position)
13
+ :type => :clustered,
14
+ :key => [
15
+ [:get_uint64], # source_id
16
+ [:get_i_sint8], # state
17
+ [:get_i_sint64], # position
18
+ ],
19
+ :row => [
20
+ [:get_uint32], # updated_at
21
+ [:get_uint64], # destination_id
22
+ [:get_uint8], # count
23
+ ],
24
+ }
25
+ when [30, 34].include?(bytes_per_record) # Secondary Key
26
+ {
27
+ # INDEX (source_id, destination_id)
28
+ :type => :secondary,
29
+ :key => [
30
+ [:get_uint64], # source_id
31
+ [:get_uint64], # destination_id
32
+ ],
33
+ # PKV ([source_id], state, position)
34
+ :row => [
35
+ [:get_i_sint8], # state
36
+ [:get_i_sint64], # position
37
+ ],
38
+ }
39
+ end
40
+ end
41
+ end
7
42
 
8
- space = Innodb::Space.new(file)
9
- #space.page(page_number).dump
10
- #exit
11
-
12
- page_types = Hash.new(0)
13
- page_levels = Hash.new(0)
14
- records_per_level = {}
15
- puts "%-8s%-8s%-8s%-8s" % ["page", "index", "level", "data"]
16
- space.each_page do |page_number, page|
17
- page_types[page.type] += 1
18
- if page.type == :INDEX
19
- puts "%-8i%-8i%-8i%-8i" % [
20
- page_number,
43
+ class PageSplitTestFormatter
44
+ def format(page)
45
+ {
46
+ # PRIMARY KEY (id)
47
+ :type => :clustered,
48
+ :key => [
49
+ [:get_uint64], # id
50
+ ],
51
+ :row => [],
52
+ }
53
+ end
54
+ end
55
+
56
+ def digraph_index(index)
57
+ puts "digraph btree {"
58
+ puts " rankdir = LR;"
59
+ puts " ranksep = 2.0;"
60
+ index.recurse(
61
+ lambda do |page, depth|
62
+ if true #page.level != 0
63
+ label = "<page>Page %i|(%i records)" % [
64
+ page.offset,
65
+ page.page_header[:n_recs],
66
+ ]
67
+ page.each_child_page do |child_page_number, child_key|
68
+ label += "|<dir_%i>(%s)" % [
69
+ child_page_number,
70
+ child_key.join(", "),
71
+ ]
72
+ end
73
+ puts " %spage_%i [ shape = \"record\"; label = \"%s\"; ];" % [
74
+ " " * depth,
75
+ page.offset,
76
+ label,
77
+ ]
78
+ end
79
+ end,
80
+ lambda do |parent_page, child_page, child_key, depth|
81
+ if true #child_page.level > 0
82
+ puts " %spage_%i:dir_%i -> page_%i:page:nw;" % [
83
+ " " * depth,
84
+ parent_page.offset,
85
+ child_page.offset,
86
+ child_page.offset,
87
+ ]
88
+ end
89
+ end
90
+ )
91
+ puts "}"
92
+ end
93
+
94
+ def recurse_index(index)
95
+ index.recurse(
96
+ lambda do |page, depth|
97
+ puts "%s%s %i: %i records, %i bytes" % [
98
+ " " * depth,
99
+ page.level == 0 ? "LEAF" : "NODE",
100
+ page.offset,
101
+ page.page_header[:n_recs],
102
+ page.record_space,
103
+ ]
104
+ if page.level == 0
105
+ page.each_record do |record|
106
+ puts "%sRECORD: (%s) -> (%s)" % [
107
+ " " * (depth+1),
108
+ record[:key].join(", "),
109
+ record[:row].join(", "),
110
+ ]
111
+ end
112
+ end
113
+ end,
114
+ lambda do |parent_page, child_page, child_min_key, depth|
115
+ puts "%sLINK %i -> %i: %s records >= (%s)" % [
116
+ " " * depth,
117
+ parent_page.offset,
118
+ child_page.offset,
119
+ child_page.page_header[:n_recs],
120
+ child_min_key.join(", "),
121
+ ]
122
+ end
123
+ )
124
+ end
125
+
126
+ def dump_space(space)
127
+ puts "%-8s%-8s%-8s%-8s%-8s%-8s" % [
128
+ "page",
129
+ "index",
130
+ "level",
131
+ "data",
132
+ "free",
133
+ "records",
134
+ ]
135
+
136
+ space.each_page do |page_number, page|
137
+ case page.type
138
+ when :INDEX
139
+ puts "%-8i%-8i%-8i%-8i%-8i%-8i" % [
140
+ page_number,
141
+ page.ph[:index_id],
142
+ page.ph[:level],
143
+ page.record_space,
144
+ page.free_space,
145
+ page.ph[:n_recs],
146
+ ]
147
+ when :ALLOCATED
148
+ puts "%-8i%-8i%-8i%-8i%-8i%-8i" % [ page_number, 0, 0, 0, Innodb::Space::PAGE_SIZE, 0 ]
149
+ end
150
+ end
151
+ end
152
+
153
+ def dump_index_level(index, level)
154
+ puts "%-8s%-8s%-8s%-8s%-8s%-8s%-8s" % [
155
+ "page",
156
+ "index",
157
+ "level",
158
+ "data",
159
+ "free",
160
+ "records",
161
+ "min_key",
162
+ ]
163
+
164
+ index.each_page_at_level(level) do |page|
165
+ puts "%-8i%-8i%-8i%-8i%-8i%-8i%s" % [
166
+ page.offset,
21
167
  page.ph[:index_id],
22
168
  page.ph[:level],
23
169
  page.record_space,
170
+ page.free_space,
171
+ page.ph[:n_recs],
172
+ page.first_record[:key].join("|"),
24
173
  ]
25
- #page_levels[page.ph[:index_id]] ||= Hash.new(0)
26
- #page_levels[page.ph[:index_id]][page.ph[:level]] += 1
27
- #records_per_level[page.ph[:level]] ||= Hash.new(0)
28
- #records_per_level[page.ph[:level]][page.ph[:n_recs]] += 1
29
- end
30
- if page.type == :ALLOCATED
31
- puts "%-8i%-8i%-8i%-8i" % [ page_number, 0, 0, 0 ]
32
174
  end
33
175
  end
176
+
177
+ file, page_number = ARGV.shift(2)
178
+
179
+ formatter = EdgesRecordFormatter.new
180
+ #formatter = PageSplitTestFormatter.new
181
+ space = Innodb::Space.new(file)
182
+ space.record_formatter = formatter
183
+ space.page(page_number).dump
184
+ #space.each_page { |page_number, page| puts "%6i%20s" % [page_number, page.type] }
185
+ #dump_space(space)
186
+ #dump_index_level(Innodb::Index.new(space, page_number), 1)
187
+ #recurse_index(Innodb::Index.new(space, page_number))
188
+ #digraph_index(Innodb::Index.new(space, page_number))
189
+ #Innodb::Index.new(space, page_number).each_record { |record| puts "(#{record[:key].join(", ")}) -> (#{record[:row].join(", ")})" }
data/lib/innodb.rb CHANGED
@@ -3,6 +3,10 @@ module Innodb; end
3
3
 
4
4
  require "innodb/version"
5
5
  require "innodb/page"
6
+ require "innodb/page/fsp_hdr_xdes"
7
+ require "innodb/page/inode"
8
+ require "innodb/page/index"
6
9
  require "innodb/space"
10
+ require "innodb/index"
7
11
  require "innodb/log_block"
8
12
  require "innodb/log"
data/lib/innodb/cursor.rb CHANGED
@@ -82,6 +82,11 @@ class Innodb::Cursor
82
82
  read_and_advance(length)
83
83
  end
84
84
 
85
+ # Return raw bytes as hex.
86
+ def get_hex(length)
87
+ read_and_advance(length).bytes.map { |c| "%02x" % c }.join
88
+ end
89
+
85
90
  # Return a big-endian unsigned 8-bit integer.
86
91
  def get_uint8(offset=nil)
87
92
  seek(offset)
@@ -145,4 +150,16 @@ class Innodb::Cursor
145
150
  raise "Invalid flag #{flag.to_s(16)} seen"
146
151
  end
147
152
  end
153
+
154
+ # Return an InnoDB-munged signed 8-bit integer. (This is only implemented
155
+ # for positive integers at the moment.)
156
+ def get_i_sint8
157
+ get_uint8 ^ (1 << 7)
158
+ end
159
+
160
+ # Return an InnoDB-munged signed 64-bit integer. (This is only implemented
161
+ # for positive integers at the moment.)
162
+ def get_i_sint64
163
+ get_uint64 ^ (1 << 63)
164
+ end
148
165
  end
data/lib/innodb/page.rb CHANGED
@@ -1,36 +1,33 @@
1
1
  require "innodb/cursor"
2
2
 
3
3
  class Innodb::Page
4
- # Currently only 16kB InnoDB pages are supported.
5
- PAGE_SIZE = 16384
4
+ SPECIALIZED_CLASSES = {}
6
5
 
7
- # InnoDB Page Type constants from include/fil0fil.h.
8
- PAGE_TYPE = {
9
- 0 => :ALLOCATED, # Freshly allocated page
10
- 2 => :UNDO_LOG, # Undo log page
11
- 3 => :INODE, # Index node
12
- 4 => :IBUF_FREE_LIST, # Insert buffer free list
13
- 5 => :IBUF_BITMAP, # Insert buffer bitmap
14
- 6 => :SYS, # System page
15
- 7 => :TRX_SYS, # Transaction system data
16
- 8 => :FSP_HDR, # File space header
17
- 9 => :XDES, # Extent descriptor page
18
- 10 => :BLOB, # Uncompressed BLOB page
19
- 11 => :ZBLOB, # First compressed BLOB page
20
- 12 => :ZBLOB2, # Subsequent compressed BLOB page
21
- 17855 => :INDEX, # B-tree node
22
- }
6
+ # Load a page as a generic page in order to make the "fil" header accessible,
7
+ # and then attempt to hand off the page to a specialized class to be
8
+ # re-parsed if possible. If there is no specialized class for this type
9
+ # of page, return the generic object.
10
+ def self.parse(buffer)
11
+ page = Innodb::Page.new(buffer)
12
+
13
+ if specialized_class = SPECIALIZED_CLASSES[page.type]
14
+ page = specialized_class.new(buffer)
15
+ end
16
+
17
+ page
18
+ end
23
19
 
24
20
  # Initialize a page by passing in a 16kB buffer containing the raw page
25
21
  # contents. Currently only 16kB pages are supported.
26
22
  def initialize(buffer)
27
- unless buffer.size == PAGE_SIZE
28
- raise "Page buffer provided was not #{PAGE_SIZE} bytes"
29
- end
30
-
31
23
  @buffer = buffer
32
24
  end
33
25
 
26
+ # Return the page size, to eventually be able to deal with non-16kB pages.
27
+ def size
28
+ @size ||= @buffer.size
29
+ end
30
+
34
31
  # A helper function to return bytes from the page buffer based on offset
35
32
  # and length, both in bytes.
36
33
  def data(offset, length)
@@ -42,23 +39,59 @@ class Innodb::Page
42
39
  Innodb::Cursor.new(self, offset)
43
40
  end
44
41
 
45
- FIL_HEADER_SIZE = 38
46
- FIL_HEADER_START = 0
42
+ # Return the byte offset of the start of the "fil" header, which is at the
43
+ # beginning of the page. Included here primarily for completeness.
44
+ def pos_fil_header
45
+ 0
46
+ end
47
+
48
+ # Return the size of the "fil" header, in bytes.
49
+ def size_fil_header
50
+ 38
51
+ end
52
+
53
+ # Return the byte offset of the start of the "fil" trailer, which is at
54
+ # the end of the page.
55
+ def pos_fil_trailer
56
+ size - size_fil_trailer
57
+ end
58
+
59
+ # Return the size of the "fil" trailer, in bytes.
60
+ def size_fil_trailer
61
+ 8
62
+ end
63
+
64
+ # InnoDB Page Type constants from include/fil0fil.h.
65
+ PAGE_TYPE = {
66
+ 0 => :ALLOCATED, # Freshly allocated page
67
+ 2 => :UNDO_LOG, # Undo log page
68
+ 3 => :INODE, # Index node
69
+ 4 => :IBUF_FREE_LIST, # Insert buffer free list
70
+ 5 => :IBUF_BITMAP, # Insert buffer bitmap
71
+ 6 => :SYS, # System page
72
+ 7 => :TRX_SYS, # Transaction system data
73
+ 8 => :FSP_HDR, # File space header
74
+ 9 => :XDES, # Extent descriptor page
75
+ 10 => :BLOB, # Uncompressed BLOB page
76
+ 11 => :ZBLOB, # First compressed BLOB page
77
+ 12 => :ZBLOB2, # Subsequent compressed BLOB page
78
+ 17855 => :INDEX, # B-tree node
79
+ }
47
80
 
48
81
  # A helper to convert "undefined" values stored in previous and next pointers
49
82
  # in the page header to nil.
50
- def maybe_undefined(value)
83
+ def self.maybe_undefined(value)
51
84
  value == 4294967295 ? nil : value
52
85
  end
53
86
 
54
87
  # Return the "fil" header from the page, which is common for all page types.
55
88
  def fil_header
56
- c = cursor(FIL_HEADER_START)
89
+ c = cursor(pos_fil_header)
57
90
  @fil_header ||= {
58
91
  :checksum => c.get_uint32,
59
92
  :offset => c.get_uint32,
60
- :prev => maybe_undefined(c.get_uint32),
61
- :next => maybe_undefined(c.get_uint32),
93
+ :prev => Innodb::Page.maybe_undefined(c.get_uint32),
94
+ :next => Innodb::Page.maybe_undefined(c.get_uint32),
62
95
  :lsn => c.get_uint64,
63
96
  :type => PAGE_TYPE[c.get_uint16],
64
97
  :flush_lsn => c.get_uint64,
@@ -73,6 +106,12 @@ class Innodb::Page
73
106
  fil_header[:type]
74
107
  end
75
108
 
109
+ # A helper function to return the page offset from the "fil" header, for
110
+ # easier access.
111
+ def offset
112
+ fil_header[:offset]
113
+ end
114
+
76
115
  # A helper function to return the page number of the logical previous page
77
116
  # (from the doubly-linked list from page to page) from the "fil" header,
78
117
  # for easier access.
@@ -87,227 +126,13 @@ class Innodb::Page
87
126
  fil_header[:next]
88
127
  end
89
128
 
90
- PAGE_HEADER_SIZE = 36
91
- PAGE_HEADER_START = FIL_HEADER_START + FIL_HEADER_SIZE
92
-
93
- PAGE_TRAILER_SIZE = 16
94
- PAGE_TRAILER_START = PAGE_SIZE - PAGE_TRAILER_SIZE
95
-
96
- FSEG_HEADER_SIZE = 10
97
- FSEG_HEADER_START = PAGE_HEADER_START + PAGE_HEADER_SIZE
98
- FSEG_HEADER_COUNT = 2
99
-
100
- MUM_RECORD_SIZE = 8
101
-
102
- RECORD_BITS_SIZE = 3
103
- RECORD_NEXT_SIZE = 2
104
-
105
- # Page direction values possible in the page_header[:direction] field.
106
- PAGE_DIRECTION = {
107
- 1 => :left,
108
- 2 => :right,
109
- 3 => :same_rec,
110
- 4 => :same_page,
111
- 5 => :no_direction,
112
- }
113
-
114
- # Return the size of the header for each record.
115
- def size_record_header
116
- case page_header[:format]
117
- when :compact
118
- RECORD_BITS_SIZE + RECORD_NEXT_SIZE
119
- when :redundant
120
- RECORD_BITS_SIZE + RECORD_NEXT_SIZE + 1
121
- end
122
- end
123
-
124
- # Return the size of a field in the record header for which no description
125
- # could be found (but must be skipped anyway).
126
- def size_record_undefined
127
- case page_header[:format]
128
- when :compact
129
- 0
130
- when :redundant
131
- 1
132
- end
133
- end
134
-
135
- # Return the "page" header; currently only "INDEX" pages are supported.
136
- def page_header
137
- return nil unless type == :INDEX
138
-
139
- c = cursor(PAGE_HEADER_START)
140
- @page_header ||= {
141
- :n_dir_slots => c.get_uint16,
142
- :heap_top => c.get_uint16,
143
- :n_heap => ((n_heap = c.get_uint16) & (2**15-1)),
144
- :free => c.get_uint16,
145
- :garbage => c.get_uint16,
146
- :last_insert => c.get_uint16,
147
- :direction => PAGE_DIRECTION[c.get_uint16],
148
- :n_direction => c.get_uint16,
149
- :n_recs => c.get_uint16,
150
- :max_trx_id => c.get_uint64,
151
- :level => c.get_uint16,
152
- :index_id => c.get_uint64,
153
- :format => (n_heap & 1<<15) == 0 ? :redundant : :compact,
154
- }
155
- end
156
- alias :ph :page_header
157
-
158
- # Parse and return simple fixed-format system records, such as InnoDB's
159
- # internal infimum and supremum records.
160
- def system_record(offset)
161
- return nil unless type == :INDEX
162
-
163
- c = cursor(offset)
164
- c.adjust(-2)
165
- {
166
- :next => offset + c.get_sint16,
167
- :data => c.get_bytes(8),
168
- }
169
- end
170
-
171
- # Return the byte offset of the start of the "origin" of the infimum record,
172
- # which is always the first record in the singly-linked record chain on any
173
- # page, and represents a record with a "lower value than any possible user
174
- # record". The infimum record immediately follows the page header.
175
- def pos_infimum
176
- pos_records + size_record_header + size_record_undefined
177
- end
178
-
179
- # Return the infimum record on a page.
180
- def infimum
181
- @infimum ||= system_record(pos_infimum)
182
- end
183
-
184
- # Return the byte offset of the start of the "origin" of the supremum record,
185
- # which is always the last record in the singly-linked record chain on any
186
- # page, and represents a record with a "higher value than any possible user
187
- # record". The supremum record immediately follows the infimum record.
188
- def pos_supremum
189
- pos_infimum + size_record_header + size_record_undefined + MUM_RECORD_SIZE
190
- end
191
-
192
- # Return the supremum record on a page.
193
- def supremum
194
- @supremum ||= system_record(pos_supremum)
195
- end
196
-
197
- # Return the byte offset of the start of records within the page (the
198
- # position immediately after the page header).
199
- def pos_records
200
- FIL_HEADER_SIZE +
201
- PAGE_HEADER_SIZE +
202
- (FSEG_HEADER_COUNT * FSEG_HEADER_SIZE)
203
- end
204
-
205
- # Return the byte offset of the start of the user records in a page, which
206
- # immediately follows the supremum record.
207
- def pos_user_records
208
- pos_supremum + size_record_header + size_record_undefined + MUM_RECORD_SIZE
209
- end
210
-
211
- # Return the amount of free space in the page.
212
- def free_space
213
- unused_space = (PAGE_TRAILER_START - page_header[:heap_top])
214
- unused_space + page_header[:garbage]
215
- end
216
-
217
- # Return the amount of used space in the page.
218
- def used_space
219
- PAGE_SIZE - free_space
220
- end
221
-
222
- # Return the amount of space occupied by records in the page.
223
- def record_space
224
- used_space - pos_user_records
225
- end
226
-
227
- # Return the actual bytes of the portion of the page which is used to
228
- # store user records (eliminate the headers and trailer from the page).
229
- def record_bytes
230
- data(pos_user_records, page_header[:heap_top] - pos_user_records)
231
- end
232
-
233
- # Return the header from a record. (This is mostly unimplemented.)
234
- def record_header(offset)
235
- return nil unless type == :INDEX
236
-
237
- c = cursor(offset).backward
238
- case page_header[:format]
239
- when :compact
240
- header = {}
241
- header[:next] = c.get_sint16
242
- bits1 = c.get_uint16
243
- header[:type] = bits1 & 0x07
244
- header[:order] = (bits1 & 0xf8) >> 3
245
- bits2 = c.get_uint8
246
- header[:n_owned] = bits2 & 0x0f
247
- header[:deleted] = (bits2 & 0xf0) >> 4
248
- header
249
- when :redundant
250
- raise "Not implemented"
251
- end
252
- end
253
-
254
- # Return a record. (This is mostly unimplemented.)
255
- def record(offset)
256
- return nil unless offset
257
- return nil unless type == :INDEX
258
- return nil if offset == pos_infimum
259
- return nil if offset == pos_supremum
260
-
261
- c = cursor(offset).forward
262
- # There is a header preceding the row itself, so back up and read it.
263
- header = record_header(offset)
264
- {
265
- :header => header,
266
- :next => header[:next] == 0 ? nil : (offset + header[:next]),
267
- # These system records may not be present depending on schema.
268
- :rec1 => c.get_bytes(6),
269
- :rec2 => c.get_bytes(6),
270
- :rec3 => c.get_bytes(7),
271
- # Read a few bytes just so it can be visually verified.
272
- :data => c.get_bytes(8),
273
- }
274
- end
275
-
276
- # Iterate through all records. (This is mostly unimplemented.)
277
- def each_record
278
- rec = infimum
279
- while rec = record(rec[:next])
280
- yield rec
281
- end
282
- nil
283
- end
284
-
285
129
  # Dump the contents of a page for debugging purposes.
286
130
  def dump
287
131
  puts
288
- puts "fil header:"
289
- pp fil_header
132
+ puts "#{self}:"
290
133
 
291
134
  puts
292
- puts "page header:"
293
- pp page_header
294
-
295
- puts
296
- puts "free space: #{free_space}"
297
- puts "used space: #{used_space}"
298
- puts "record space: #{record_space}"
299
-
300
- if type == :INDEX
301
- puts
302
- puts "system records:"
303
- pp infimum
304
- pp supremum
305
-
306
- puts
307
- puts "records:"
308
- each_record do |rec|
309
- pp rec
310
- end
311
- end
135
+ puts "fil header:"
136
+ pp fil_header
312
137
  end
313
138
  end
data/lib/innodb/space.rb CHANGED
@@ -1,21 +1,33 @@
1
1
  # An InnoDB tablespace file, which can be either a multi-table ibdataN file
2
2
  # or a single-table "innodb_file_per_table" .ibd file.
3
3
  class Innodb::Space
4
+ attr_accessor :record_formatter
5
+
6
+ # Currently only 16kB InnoDB pages are supported.
7
+ PAGE_SIZE = 16384
8
+
4
9
  # Open a tablespace file.
5
10
  def initialize(file)
6
11
  @file = File.open(file)
7
12
  @size = @file.stat.size
8
- @pages = (@size / Innodb::Page::PAGE_SIZE)
13
+ @pages = (@size / PAGE_SIZE)
14
+ @record_formatter = nil
9
15
  end
10
16
 
11
17
  # Get an Innodb::Page object for a specific page by page number.
12
18
  def page(page_number)
13
- offset = page_number.to_i * Innodb::Page::PAGE_SIZE
19
+ offset = page_number.to_i * PAGE_SIZE
14
20
  return nil unless offset < @size
15
- return nil unless (offset + Innodb::Page::PAGE_SIZE) <= @size
21
+ return nil unless (offset + PAGE_SIZE) <= @size
16
22
  @file.seek(offset)
17
- page_data = @file.read(Innodb::Page::PAGE_SIZE)
18
- Innodb::Page.new(page_data)
23
+ page_data = @file.read(PAGE_SIZE)
24
+ this_page = Innodb::Page.parse(page_data)
25
+
26
+ if this_page.type == :INDEX
27
+ this_page.record_formatter = @record_formatter
28
+ end
29
+
30
+ this_page
19
31
  end
20
32
 
21
33
  # Iterate through all pages in a tablespace, returning the page number
@@ -1,3 +1,3 @@
1
1
  module Innodb
2
- VERSION = "0.4"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: innodb_ruby
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- version: "0.4"
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
10
11
  platform: ruby
11
12
  authors:
12
13
  - Jeremy Cole
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2012-11-20 00:00:00 Z
18
+ date: 2012-11-29 00:00:00 Z
18
19
  dependencies: []
19
20
 
20
21
  description: Library for parsing InnoDB data files in Ruby