innodb_ruby 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2dd793281e3b83403e55c8d4e9104898d5df6099af80dd4453795a524e3fb32
4
- data.tar.gz: d6ac28ab11759a618f9a84af611a705644f5f47f9c1cd387360b628bac88f527
3
+ metadata.gz: 65c78efba30c942c5529781a0dc7b36816af82d8aa4077d29d535f0727485c02
4
+ data.tar.gz: 6eb04e7370deee2f220383b36dd9d1dd1d8c454024f7a20fa2a3a8ece386bb14
5
5
  SHA512:
6
- metadata.gz: ffb5ba4b9805d4cb78aaecaec92adb4e6064d73a9349fbeb43fcc938b958adeb14f140747dd540f76d31bbf9a38fbde259bd99f3a9cc64f32a1456efc2006997
7
- data.tar.gz: 9e98bd61fe4448fdbb15be66975581fe65d97c342685c21f2309ead8b0ed1fc217b08baf2855c336d8ea426e22b7c0556e0926064722e6cc23a95312a6a63ca4
6
+ metadata.gz: ac98dfc4efeb3e42941bba41b021037e480e53a25300f3d4d115eae44e2c6435592f00f2d65934a0e14166e93fc2dd9e376189bd0c8945a138b0e4ece3a16b59
7
+ data.tar.gz: 449fb6bd1c19690a52aaa065efa6f2465a5aae66be3e1a42b69c2826c8d489babd4979e5ef2c8e840b81629c965d0731da72bc358845306e76b15efa81c34b80
data/bin/innodb_log CHANGED
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "getoptlong"
5
- require "ostruct"
6
5
  require "set"
7
6
  require "innodb"
8
7
 
@@ -60,7 +59,7 @@ def usage(exit_code, message = nil)
60
59
  print "Error: #{message}\n" unless message.nil?
61
60
 
62
61
  # rubocop:disable Layout/HeredocIndentation
63
- print <<'END_OF_USAGE'
62
+ print <<END_OF_USAGE
64
63
 
65
64
  Usage: innodb_log [-d] [-l <lsn>] -f <log file> <mode>
66
65
 
@@ -96,7 +95,14 @@ END_OF_USAGE
96
95
  exit exit_code
97
96
  end
98
97
 
99
- @options = OpenStruct.new
98
+ InnodbLogOptions = Struct.new(
99
+ :log_files,
100
+ :dump,
101
+ :lsn,
102
+ keyword_init: true
103
+ )
104
+
105
+ @options = InnodbLogOptions.new
100
106
  @options.log_files = []
101
107
  @options.dump = false
102
108
  @options.lsn = nil
data/bin/innodb_space CHANGED
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "getoptlong"
5
- require "ostruct"
6
5
  require "histogram/array"
7
6
  require "innodb"
8
7
 
@@ -118,10 +117,10 @@ def print_lists(lists)
118
117
  puts "%-20s%-12i%-12i%-12i%-12i%-12i" % [
119
118
  name,
120
119
  list.base[:length],
121
- list.base[:first] && list.base[:first][:page] || 0,
122
- list.base[:first] && list.base[:first][:offset] || 0,
123
- list.base[:last] && list.base[:last][:page] || 0,
124
- list.base[:last] && list.base[:last][:offset] || 0,
120
+ (list.base[:first] && list.base[:first][:page]) || 0,
121
+ (list.base[:first] && list.base[:first][:offset]) || 0,
122
+ (list.base[:last] && list.base[:last][:page]) || 0,
123
+ (list.base[:last] && list.base[:last][:offset]) || 0,
125
124
  ]
126
125
  end
127
126
  end
@@ -157,8 +156,8 @@ def print_index_page_summary(pages)
157
156
  ]
158
157
 
159
158
  pages.each do |page_number, page|
160
- case page.type
161
- when :INDEX
159
+ case
160
+ when page.is_a?(Innodb::Page::Index)
162
161
  puts "%-12i%-8i%-8i%-8i%-8i%-8i" % [
163
162
  page_number,
164
163
  page.page_header[:index_id],
@@ -167,7 +166,7 @@ def print_index_page_summary(pages)
167
166
  page.free_space,
168
167
  page.records,
169
168
  ]
170
- when :ALLOCATED
169
+ when page.type == :ALLOCATED
171
170
  puts "%-12i%-8i%-8i%-8i%-8i%-8i" % [page_number, 0, 0, 0, page.size, 0]
172
171
  end
173
172
  end
@@ -430,12 +429,12 @@ def space_index_pages_free_plot(space, start_page)
430
429
  index_data = { 0 => { x: [], y: [] } }
431
430
 
432
431
  space.each_page(start_page) do |page_number, page|
433
- case page.type
434
- when :INDEX
432
+ case
433
+ when page.is_a?(Innodb::Page::Index)
435
434
  data = (index_data[page.page_header[:index_id]] ||= { x: [], y: [] })
436
435
  data[:x] << page_number
437
436
  data[:y] << page.free_space
438
- when :ALLOCATED
437
+ when page.type == :ALLOCATED
439
438
  index_data[0][:x] << page_number
440
439
  index_data[0][:y] << page.size
441
440
  end
@@ -445,7 +444,7 @@ def space_index_pages_free_plot(space, start_page)
445
444
  image_file = "#{image_name}_free.png"
446
445
 
447
446
  # Aim for one horizontal pixel per extent, but min 1k and max 10k width.
448
- image_width = [10_000, [1_000, space.pages / space.pages_per_extent].max].min
447
+ (space.pages / space.pages_per_extent).clamp(1_000, 10_000)
449
448
 
450
449
  Gnuplot.open do |gp|
451
450
  Gnuplot::Plot.new(gp) do |plot|
@@ -476,7 +475,7 @@ end
476
475
 
477
476
  # rubocop:disable Metrics/BlockNesting
478
477
  def space_extents_illustrate_page_status(space, entry, count_by_identifier, identifiers)
479
- entry.each_page_status.each_with_object("".dup) do |(page_number, page_status), bitmap|
478
+ entry.each_page_status.with_object("".dup) do |(page_number, page_status), bitmap|
480
479
  if page_number >= space.pages
481
480
  bitmap << " "
482
481
  next
@@ -577,8 +576,8 @@ def space_lsn_age_illustrate(space)
577
576
  next if page.lsn.zero?
578
577
 
579
578
  page_lsn[page_number] = page.lsn
580
- lsn_min = page.lsn < lsn_min ? page.lsn : lsn_min
581
- lsn_max = page.lsn > lsn_max ? page.lsn : lsn_max
579
+ lsn_min = [page.lsn, lsn_min].min
580
+ lsn_max = [page.lsn, lsn_max].max
582
581
  end
583
582
  lsn_delta = lsn_max - lsn_min
584
583
 
@@ -606,7 +605,7 @@ def space_lsn_age_illustrate(space)
606
605
 
607
606
  puts "%12s ╰%-#{width}s╯" % ["", "─" * width]
608
607
 
609
- _, lsn_freq = page_lsn.reject(&:nil?).histogram(colors.size, min: lsn_min, max: lsn_max)
608
+ _, lsn_freq = page_lsn.compact.histogram(colors.size, min: lsn_min, max: lsn_max)
610
609
  lsn_freq_delta = lsn_freq.max - lsn_freq.min
611
610
 
612
611
  lsn_age_histogram = "".dup
@@ -690,10 +689,9 @@ def page_account(innodb_system, space, page_number)
690
689
 
691
690
  page = space.page(page_number)
692
691
  page_type = Innodb::Page::PAGE_TYPE[page.type]
693
- puts " Page type is %s (%s, %s)." % [
692
+ puts " Page type is %s (%s)." % [
694
693
  page.type,
695
694
  page_type[:description],
696
- page_type[:usage],
697
695
  ]
698
696
 
699
697
  xdes = space.xdes_for_page(page_number)
@@ -922,7 +920,7 @@ def page_validate(_innodb_system, space, page_number)
922
920
  end
923
921
  end
924
922
 
925
- page_is_valid = false if page.type == :INDEX && !page_validate_index(page)
923
+ page_is_valid = false if page.is_a?(Innodb::Page::Index) && !page_validate_index(page)
926
924
 
927
925
  puts "Page %d appears to be %s!" % [
928
926
  page_number,
@@ -931,7 +929,7 @@ def page_validate(_innodb_system, space, page_number)
931
929
  end
932
930
 
933
931
  def page_directory_summary(_space, page)
934
- usage(1, "Page must be an index page") if page.type != :INDEX
932
+ usage(1, "Page must be an index page") unless page.is_a?(Innodb::Page::Index)
935
933
 
936
934
  puts "%-8s%-8s%-14s%-8s%s" % %w[
937
935
  slot
@@ -966,7 +964,7 @@ end
966
964
 
967
965
  def page_illustrate(page)
968
966
  width = 64
969
- unknown_page_content = page.type == :INDEX && page.record_describer.nil?
967
+ unknown_page_content = page.is_a?(Innodb::Page::Index) && page.record_describer.nil?
970
968
  blocks = Array.new(page.size, unknown_page_content ? "▞" : " ")
971
969
  identifiers = {}
972
970
  identifier_sort = 0
@@ -1251,7 +1249,7 @@ def usage(exit_code, message = nil)
1251
1249
  end
1252
1250
 
1253
1251
  # rubocop:disable Layout/HeredocIndentation
1254
- print <<'END_OF_USAGE'
1252
+ print <<END_OF_USAGE
1255
1253
 
1256
1254
  Usage: innodb_space <options> <mode>
1257
1255
 
@@ -1484,7 +1482,26 @@ end
1484
1482
  Signal.trap(name) { exit } if Signal.list.include?(name)
1485
1483
  end
1486
1484
 
1487
- @options = OpenStruct.new
1485
+ InnodbSpaceOptions = Struct.new(
1486
+ :trace,
1487
+ :system_space_file,
1488
+ :system_space_tables,
1489
+ :data_directory,
1490
+ :space_file,
1491
+ :table_name,
1492
+ :index_name,
1493
+ :page,
1494
+ :record,
1495
+ :level,
1496
+ :list,
1497
+ :fseg_id,
1498
+ :describer,
1499
+ :illustration_line_width,
1500
+ :illustration_block_size,
1501
+ keyword_init: true
1502
+ )
1503
+
1504
+ @options = InnodbSpaceOptions.new
1488
1505
  @options.trace = 0
1489
1506
  @options.system_space_file = nil
1490
1507
  @options.system_space_tables = false
@@ -1625,7 +1642,7 @@ if innodb_system && @options.table_name && @options.index_name
1625
1642
  page = @options.page ? space.page(@options.page) : index.root
1626
1643
  elsif @options.page
1627
1644
  page = space.page(@options.page)
1628
- index = space.index(@options.page) if page&.type == :INDEX && page&.root?
1645
+ index = space.index(@options.page) if page.is_a?(Innodb::Page::Index) && page&.root?
1629
1646
  end
1630
1647
 
1631
1648
  # The non-option argument on the command line is the mode (usually the last,
@@ -1678,7 +1695,7 @@ if /^record-/.match(mode) && !page
1678
1695
  )
1679
1696
  end
1680
1697
 
1681
- if /^record-/.match(mode) && page.type != :INDEX
1698
+ if /^record-/.match(mode) && !page.is_a?(Innodb::Page::Index)
1682
1699
  usage(1, "Mode #{mode} may be used only with index pages")
1683
1700
  end
1684
1701
 
@@ -235,7 +235,14 @@ module Innodb
235
235
  system_space.data_dictionary_page.data_dictionary_header[:indexes]
236
236
  end
237
237
 
238
+ # Check if the data dictionary indexes are all available.
239
+ def found?
240
+ data_dictionary_indexes.values.map(&:values).flatten.none?(&:zero?)
241
+ end
242
+
238
243
  def data_dictionary_index_ids
244
+ raise "Data Dictionary not found; is the MySQL version supported?" unless found?
245
+
239
246
  return @data_dictionary_index_ids if @data_dictionary_index_ids
240
247
 
241
248
  # TODO: This could probably be done a lot more Ruby-like.
@@ -260,7 +267,7 @@ module Innodb
260
267
  end
261
268
 
262
269
  def data_dictionary_index?(table_name, index_name)
263
- return unless data_dictionary_table?(table_name)
270
+ return false unless data_dictionary_table?(table_name)
264
271
 
265
272
  DATA_DICTIONARY_RECORD_DESCRIBERS[table_name.to_sym].include?(index_name.to_sym)
266
273
  end
@@ -275,6 +282,8 @@ module Innodb
275
282
  # internal data dictionary index with an appropriate
276
283
  # record describer so that records can be recursed.
277
284
  def data_dictionary_index(table_name, index_name)
285
+ raise "Data Dictionary not found; is the MySQL version supported?" unless found?
286
+
278
287
  table_entry = data_dictionary_indexes[table_name]
279
288
  raise "Unknown data dictionary table #{table_name}" unless table_entry
280
289
 
@@ -307,7 +316,7 @@ module Innodb
307
316
  return enum_for(:each_data_dictionary_index) unless block_given?
308
317
 
309
318
  data_dictionary_indexes.each do |table_name, indexes|
310
- indexes.each do |index_name, _root_page_number|
319
+ indexes.each_key do |index_name|
311
320
  yield table_name, index_name,
312
321
  data_dictionary_index(table_name, index_name)
313
322
  end
@@ -112,8 +112,8 @@ module Innodb
112
112
  @uncomp_fractional = scale / MAX_DIGITS_PER_INTEGER
113
113
  @comp_integral = integral - (@uncomp_integral * MAX_DIGITS_PER_INTEGER)
114
114
  @comp_fractional = scale - (@uncomp_fractional * MAX_DIGITS_PER_INTEGER)
115
- @width = @uncomp_integral * 4 + BYTES_PER_DIGIT[@comp_integral] +
116
- @comp_fractional * 4 + BYTES_PER_DIGIT[@comp_fractional]
115
+ @width = (@uncomp_integral * 4) + BYTES_PER_DIGIT[@comp_integral] +
116
+ (@comp_fractional * 4) + BYTES_PER_DIGIT[@comp_fractional]
117
117
  @name = Innodb::DataType.make_name(base_type, modifiers, properties)
118
118
  end
119
119
 
data/lib/innodb/field.rb CHANGED
@@ -121,7 +121,7 @@ module Innodb
121
121
  base_type = matches[1].upcase.to_sym
122
122
  return [base_type, []] unless matches[3]
123
123
 
124
- [base_type, matches[3].sub(/ /, "").split(/,/).map(&:to_i)]
124
+ [base_type, matches[3].sub(/ /, "").split(",").map(&:to_i)]
125
125
  end
126
126
  end
127
127
  end
data/lib/innodb/index.rb CHANGED
@@ -21,7 +21,9 @@ module Innodb
21
21
  raise "Page #{root_page_number} couldn't be read" unless @root
22
22
 
23
23
  # The root page should be an index page.
24
- raise "Page #{root_page_number} is a #{@root.type} page, not an INDEX page" unless @root.type == :INDEX
24
+ unless @root.is_a?(Innodb::Page::Index)
25
+ raise "Page #{root_page_number} is a #{@root.type} page, not an INDEX page"
26
+ end
25
27
 
26
28
  # The root page should be the only page at its level.
27
29
  raise "Page #{root_page_number} does not appear to be an index root" if @root.prev || @root.next
@@ -53,12 +55,12 @@ module Innodb
53
55
 
54
56
  # Internal method used by recurse.
55
57
  def _recurse(parent_page, page_proc, link_proc, depth = 0)
56
- page_proc.call(parent_page, depth) if page_proc && parent_page.type == :INDEX
58
+ page_proc.call(parent_page, depth) if page_proc && parent_page.is_a?(Innodb::Page::Index)
57
59
 
58
60
  parent_page.each_child_page do |child_page_number, child_min_key|
59
61
  child_page = page(child_page_number)
60
62
  child_page.record_describer = record_describer
61
- next unless child_page.type == :INDEX
63
+ next unless child_page.is_a?(Innodb::Page::Index)
62
64
 
63
65
  link_proc&.call(parent_page, child_page, child_min_key, depth + 1)
64
66
  _recurse(child_page, page_proc, link_proc, depth + 1)
@@ -143,7 +145,7 @@ module Innodb
143
145
  def each_page_from(page)
144
146
  return enum_for(:each_page_from, page) unless block_given?
145
147
 
146
- while page && page.type == :INDEX
148
+ while page.is_a?(Innodb::Page::Index)
147
149
  yield page
148
150
  break unless page.next
149
151
 
data/lib/innodb/inode.rb CHANGED
@@ -99,7 +99,7 @@ module Innodb
99
99
 
100
100
  # Helper method to return an array of only non-nil fragment pages.
101
101
  def frag_array_pages
102
- frag_array.reject(&:nil?)
102
+ frag_array.compact
103
103
  end
104
104
 
105
105
  # Helper method to count non-nil fragment pages.
data/lib/innodb/lsn.rb CHANGED
@@ -40,7 +40,7 @@ module Innodb
40
40
  fragment = (@lsn_no % LOG_BLOCK_SIZE) - LOG_BLOCK_HEADER_SIZE
41
41
  raise "Invalid fragment #{fragment} for LSN #{@lsn_no}" unless fragment.between?(0, LOG_BLOCK_DATA_SIZE - 1)
42
42
 
43
- length + (fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE
43
+ length + ((fragment + length) / LOG_BLOCK_DATA_SIZE * LOG_BLOCK_FRAME_SIZE)
44
44
  end
45
45
 
46
46
  # Whether LSN might point to log record data.
@@ -82,12 +82,12 @@ module Innodb
82
82
  end
83
83
 
84
84
  # Transpose group size offset to a group capacity offset.
85
- group_offset = offset - (LOG_HEADER_SIZE * (1 + offset / log_size))
85
+ group_offset = offset - (LOG_HEADER_SIZE * (1 + (offset / log_size)))
86
86
 
87
87
  offset = (lsn_offset + group_offset) % group_capacity
88
88
 
89
89
  # Transpose group capacity offset to a group size offset.
90
- offset + LOG_HEADER_SIZE * (1 + offset / (log_size - LOG_HEADER_SIZE))
90
+ offset + (LOG_HEADER_SIZE * (1 + (offset / (log_size - LOG_HEADER_SIZE))))
91
91
  end
92
92
 
93
93
  # Whether offset points to the data area of an existing log block.
@@ -32,22 +32,16 @@ module Innodb
32
32
  end
33
33
  end
34
34
 
35
- def dump_hex(string)
36
- slice_size = 16
37
- string.chars.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
38
- puts "%08i %-23s %-23s |%-16s|" % [
39
- (slice_count * slice_size),
40
- slice_bytes[0..8].map { |n| "%02x" % n.ord }.join(" "),
41
- slice_bytes[8..16].map { |n| "%02x" % n.ord }.join(" "),
42
- slice_bytes.join,
43
- ]
44
- end
35
+ def next_blob_page
36
+ return unless blob_header[:next]
37
+
38
+ space.page(blob_header[:next])
45
39
  end
46
40
 
47
41
  def each_region(&block)
48
42
  return enum_for(:each_region) unless block_given?
49
43
 
50
- super(&block)
44
+ super
51
45
 
52
46
  yield Region.new(
53
47
  offset: pos_blob_header,
@@ -75,7 +69,7 @@ module Innodb
75
69
  puts
76
70
 
77
71
  puts "blob data:"
78
- dump_hex(blob_data)
72
+ HexFormat.puts(blob_data)
79
73
  puts
80
74
 
81
75
  puts
@@ -47,6 +47,22 @@ module Innodb
47
47
  keyword_init: true
48
48
  )
49
49
 
50
+ EncryptionHeader = Struct.new(
51
+ :magic,
52
+ :master_key_id,
53
+ :key,
54
+ :iv,
55
+ :server_uuid,
56
+ :checksum,
57
+ keyword_init: true
58
+ )
59
+
60
+ SdiHeader = Struct.new(
61
+ :version,
62
+ :root_page_number,
63
+ keyword_init: true
64
+ )
65
+
50
66
  # A value added to the adjusted exponent stored in the page size field of
51
67
  # the flags in the FSP header.
52
68
  FLAGS_PAGE_SIZE_SHIFT = 9
@@ -118,6 +134,22 @@ module Innodb
118
134
  entries_in_xdes_array * size_xdes_entry
119
135
  end
120
136
 
137
+ def pos_encryption_header
138
+ pos_xdes_array + size_xdes_array
139
+ end
140
+
141
+ def size_encryption_header
142
+ 3 + 4 + (32 * 2) + 36 + 4 + 4
143
+ end
144
+
145
+ def pos_sdi_header
146
+ pos_encryption_header + size_encryption_header
147
+ end
148
+
149
+ def size_sdi_header
150
+ 8
151
+ end
152
+
121
153
  # Read the FSP (filespace) header, which contains a few counters and flags,
122
154
  # as well as list base nodes for each list maintained in the filespace.
123
155
  def fsp_header
@@ -162,10 +194,32 @@ module Innodb
162
194
  end
163
195
  end
164
196
 
197
+ def encryption_header
198
+ @encryption_header ||= cursor(pos_encryption_header).name("encryption_header") do |c|
199
+ EncryptionHeader.new(
200
+ magic: c.name("magic") { c.read_bytes(3) },
201
+ master_key_id: c.name("master_key_id") { c.read_uint32 },
202
+ key: c.name("key") { c.read_bytes(32) },
203
+ iv: c.name("iv") { c.read_bytes(32) },
204
+ server_uuid: c.name("server_uuid") { c.read_string(36) },
205
+ checksum: c.name("checksum") { c.read_uint32 }
206
+ )
207
+ end
208
+ end
209
+
210
+ def sdi_header
211
+ @sdi_header ||= cursor(pos_sdi_header).name("sdi_header") do |c|
212
+ SdiHeader.new(
213
+ version: c.name("version") { c.read_uint32 },
214
+ root_page_number: c.name("root_page_number") { c.read_uint32 }
215
+ )
216
+ end
217
+ end
218
+
165
219
  def each_region(&block)
166
220
  return enum_for(:each_region) unless block_given?
167
221
 
168
- super(&block)
222
+ super
169
223
 
170
224
  yield Region.new(
171
225
  offset: pos_fsp_header,
@@ -179,11 +233,25 @@ module Innodb
179
233
  yield Region.new(
180
234
  offset: xdes.offset,
181
235
  length: size_xdes_entry,
182
- name: "xdes_#{state}".to_sym,
236
+ name: :"xdes_#{state}",
183
237
  info: "Extent Descriptor (#{state})"
184
238
  )
185
239
  end
186
240
 
241
+ yield Region.new(
242
+ offset: pos_encryption_header,
243
+ length: size_encryption_header,
244
+ name: :encryption_header,
245
+ info: "Encryption Header"
246
+ )
247
+
248
+ yield Region.new(
249
+ offset: pos_sdi_header,
250
+ length: size_sdi_header,
251
+ name: :sdi_header,
252
+ info: "SDI Header"
253
+ )
254
+
187
255
  nil
188
256
  end
189
257
 
@@ -200,6 +268,14 @@ module Innodb
200
268
  pp xdes
201
269
  end
202
270
  puts
271
+
272
+ puts "encryption header:"
273
+ pp encryption_header
274
+ puts
275
+
276
+ puts "serialized dictionary information header:"
277
+ pp sdi_header
278
+ puts
203
279
  end
204
280
  end
205
281
  end
@@ -22,7 +22,7 @@ module Innodb
22
22
  def each_region(&block)
23
23
  return enum_for(:each_region) unless block_given?
24
24
 
25
- super(&block)
25
+ super
26
26
 
27
27
  yield Region.new(
28
28
  offset: pos_ibuf_bitmap,
@@ -130,7 +130,7 @@ module Innodb
130
130
  # Maximum number of fields.
131
131
  RECORD_MAX_N_SYSTEM_FIELDS = 3
132
132
  RECORD_MAX_N_FIELDS = 1024 - 1
133
- RECORD_MAX_N_USER_FIELDS = RECORD_MAX_N_FIELDS - RECORD_MAX_N_SYSTEM_FIELDS * 2
133
+ RECORD_MAX_N_USER_FIELDS = RECORD_MAX_N_FIELDS - (RECORD_MAX_N_SYSTEM_FIELDS * 2)
134
134
 
135
135
  # Page direction values possible in the page_header's :direction field.
136
136
  PAGE_DIRECTION = {
@@ -314,8 +314,8 @@ module Innodb
314
314
  index_id: c.name("index_id") { c.read_uint64 }
315
315
  )
316
316
 
317
- index.n_heap = index.n_heap_format & (2**15 - 1)
318
- index.format = (index.n_heap_format & 1 << 15).zero? ? :redundant : :compact
317
+ index.n_heap = index.n_heap_format & ((2**15) - 1)
318
+ index.format = (index.n_heap_format & (1 << 15)).zero? ? :redundant : :compact
319
319
 
320
320
  index
321
321
  end
@@ -546,7 +546,7 @@ module Innodb
546
546
  end
547
547
 
548
548
  def make_record_describer
549
- if space&.innodb_system && index_id
549
+ if space&.innodb_system&.data_dictionary&.found? && index_id && !ibuf_index?
550
550
  @record_describer = space.innodb_system.data_dictionary.record_describer_by_index_id(index_id)
551
551
  elsif space
552
552
  @record_describer = space.record_describer
@@ -998,7 +998,7 @@ module Innodb
998
998
  def each_region(&block)
999
999
  return enum_for(:each_region) unless block_given?
1000
1000
 
1001
- super(&block)
1001
+ super
1002
1002
 
1003
1003
  yield Region.new(
1004
1004
  offset: pos_index_header,
@@ -1096,17 +1096,23 @@ module Innodb
1096
1096
  pp supremum.record
1097
1097
  puts
1098
1098
 
1099
- puts "garbage records:"
1100
- each_garbage_record do |rec|
1101
- pp rec.record
1099
+ if ibuf_index?
1100
+ puts "(records not dumped due to this being an insert buffer index)"
1101
+ elsif !record_describer
1102
+ puts "(records not dumped due to missing record describer or data dictionary)"
1103
+ else
1104
+ puts "garbage records:"
1105
+ each_garbage_record do |rec|
1106
+ pp rec.record
1107
+ puts
1108
+ end
1102
1109
  puts
1103
- end
1104
- puts
1105
1110
 
1106
- puts "records:"
1107
- each_record do |rec|
1108
- pp rec.record
1109
- puts
1111
+ puts "records:"
1112
+ each_record do |rec|
1113
+ pp rec.record
1114
+ puts
1115
+ end
1110
1116
  end
1111
1117
  puts
1112
1118
  end
@@ -34,7 +34,7 @@ module Innodb
34
34
  def free_space
35
35
  free_space_start =
36
36
  size - size_fil_trailer - directory_space - (uncompressed_columns_size * (page_header.n_heap - 2))
37
- puts "Free space start == %04x" % [offset * size + free_space_start]
37
+ puts "Free space start == %04x" % [(offset * size) + free_space_start]
38
38
  c = cursor(free_space_start).backward
39
39
  zero_bytes = 0
40
40
  zero_bytes += 1 while c.read_uint8.zero?
@@ -83,7 +83,7 @@ module Innodb
83
83
  def each_region(&block)
84
84
  return enum_for(:each_region) unless block_given?
85
85
 
86
- super(&block)
86
+ super
87
87
 
88
88
  yield Region.new(
89
89
  offset: pos_list_entry,
@@ -68,7 +68,7 @@ module Innodb
68
68
  def each_region(&block)
69
69
  return enum_for(:each_region) unless block_given?
70
70
 
71
- super(&block)
71
+ super
72
72
 
73
73
  yield Region.new(
74
74
  offset: pos_data_dictionary_header,
@@ -27,7 +27,7 @@ module Innodb
27
27
  def each_region(&block)
28
28
  return enum_for(:each_region) unless block_given?
29
29
 
30
- super(&block)
30
+ super
31
31
 
32
32
  yield Region.new(
33
33
  offset: pos_ibuf_header,
@@ -66,7 +66,7 @@ module Innodb
66
66
  def each_region(&block)
67
67
  return enum_for(:each_region) unless block_given?
68
68
 
69
- super(&block)
69
+ super
70
70
 
71
71
  yield Region.new(
72
72
  offset: pos_rseg_header,
@@ -182,7 +182,7 @@ module Innodb
182
182
  def each_region(&block)
183
183
  return enum_for(:each_region) unless block_given?
184
184
 
185
- super(&block)
185
+ super
186
186
 
187
187
  yield Region.new(
188
188
  offset: pos_trx_sys_header,
data/lib/innodb/page.rb CHANGED
@@ -101,11 +101,8 @@ module Innodb
101
101
  # Initialize a page by passing in a buffer containing the raw page contents.
102
102
  # The buffer size should match the space's page size.
103
103
  def initialize(space, buffer, page_number = nil)
104
- unless space && buffer
105
- raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})"
106
- end
107
-
108
- raise "Buffer size #{buffer.size} is different than space page size" unless space.page_size == buffer.size
104
+ raise "Page can't be initialized from nil space or buffer (space: #{space}, buffer: #{buffer})" unless buffer
105
+ raise "Buffer size #{buffer.size} is different than space page size" if space && space.page_size != buffer.size
109
106
 
110
107
  @space = space
111
108
  @buffer = buffer
@@ -141,7 +138,7 @@ module Innodb
141
138
  # return value of the block.
142
139
  def cursor(buffer_offset)
143
140
  new_cursor = BufferCursor.new(@buffer, buffer_offset)
144
- new_cursor.push_name("space[#{space.name}]")
141
+ new_cursor.push_name("space[#{space&.name || 'unknown'}]")
145
142
  new_cursor.push_name("page[#{name}]")
146
143
 
147
144
  if block_given?
@@ -205,74 +202,137 @@ module Innodb
205
202
  ALLOCATED: {
206
203
  value: 0,
207
204
  description: "Freshly allocated",
208
- usage: "page type field has not been initialized",
209
205
  },
210
206
  UNDO_LOG: {
211
207
  value: 2,
212
208
  description: "Undo log",
213
- usage: "stores previous values of modified records",
214
209
  },
215
210
  INODE: {
216
211
  value: 3,
217
212
  description: "File segment inode",
218
- usage: "bookkeeping for file segments",
219
213
  },
220
214
  IBUF_FREE_LIST: {
221
215
  value: 4,
222
216
  description: "Insert buffer free list",
223
- usage: "bookkeeping for insert buffer free space management",
224
217
  },
225
218
  IBUF_BITMAP: {
226
219
  value: 5,
227
220
  description: "Insert buffer bitmap",
228
- usage: "bookkeeping for insert buffer writes to be merged",
229
221
  },
230
222
  SYS: {
231
223
  value: 6,
232
224
  description: "System internal",
233
- usage: "used for various purposes in the system tablespace",
234
225
  },
235
226
  TRX_SYS: {
236
227
  value: 7,
237
228
  description: "Transaction system header",
238
- usage: "bookkeeping for the transaction system in system tablespace",
239
229
  },
240
230
  FSP_HDR: {
241
231
  value: 8,
242
232
  description: "File space header",
243
- usage: "header page (page 0) for each tablespace file",
244
233
  },
245
234
  XDES: {
246
235
  value: 9,
247
236
  description: "Extent descriptor",
248
- usage: "header page for subsequent blocks of 16,384 pages",
249
237
  },
250
238
  BLOB: {
251
239
  value: 10,
252
240
  description: "Uncompressed BLOB",
253
- usage: "externally-stored uncompressed BLOB column data",
254
241
  },
255
242
  ZBLOB: {
256
243
  value: 11,
257
244
  description: "First compressed BLOB",
258
- usage: "externally-stored compressed BLOB column data, first page",
259
245
  },
260
246
  ZBLOB2: {
261
247
  value: 12,
262
248
  description: "Subsequent compressed BLOB",
263
- usage: "externally-stored compressed BLOB column data, subsequent page",
249
+ },
250
+ UNKNOWN: {
251
+ value: 13,
252
+ description: "Unknown",
253
+ },
254
+ COMPRESSED: {
255
+ value: 14,
256
+ description: "Compressed",
257
+ },
258
+ ENCRYPTED: {
259
+ value: 15,
260
+ description: "Encrypted",
261
+ },
262
+ COMPRESSED_AND_ENCRYPTED: {
263
+ value: 16,
264
+ description: "Compressed and Encrypted",
265
+ },
266
+ ENCRYPTED_RTREE: {
267
+ value: 17,
268
+ description: "Encrypted R-tree",
269
+ },
270
+ SDI_BLOB: {
271
+ value: 18,
272
+ description: "Uncompressed SDI BLOB",
273
+ },
274
+ SDI_ZBLOB: {
275
+ value: 19,
276
+ description: "Compressed SDI BLOB",
277
+ },
278
+ LEGACY_DBLWR: {
279
+ value: 20,
280
+ description: "Legacy doublewrite buffer",
281
+ },
282
+ RSEG_ARRAY: {
283
+ value: 21,
284
+ description: "Rollback Segment Array",
285
+ },
286
+ LOB_INDEX: {
287
+ value: 22,
288
+ description: "Index of uncompressed LOB",
289
+ },
290
+ LOB_DATA: {
291
+ value: 23,
292
+ description: "Data of uncompressed LOB",
293
+ },
294
+ LOB_FIRST: {
295
+ value: 24,
296
+ description: "First page of an uncompressed LOB",
297
+ },
298
+ ZLOB_FIRST: {
299
+ value: 25,
300
+ description: "First page of a compressed LOB",
301
+ },
302
+ ZLOB_DATA: {
303
+ value: 26,
304
+ description: "Data of compressed LOB",
305
+ },
306
+ ZLOB_INDEX: {
307
+ value: 27,
308
+ description: "Index of compressed LOB",
309
+ },
310
+ ZLOB_FRAG: {
311
+ value: 28,
312
+ description: "Fragment of compressed LOB",
313
+ },
314
+ ZLOB_FRAG_ENTRY: {
315
+ value: 29,
316
+ description: "Index of fragment for compressed LOB",
317
+ },
318
+ SDI: {
319
+ value: 17_853,
320
+ description: "Serialized Dictionary Information",
321
+ },
322
+ RTREE: {
323
+ value: 17_854,
324
+ description: "R-tree index",
264
325
  },
265
326
  INDEX: {
266
327
  value: 17_855,
267
328
  description: "B+Tree index",
268
- usage: "table and index data stored in B+Tree structure",
269
329
  },
270
330
  }.freeze
271
331
 
272
332
  PAGE_TYPE_BY_VALUE = PAGE_TYPE.each_with_object({}) { |(k, v), h| h[v[:value]] = k }
273
333
 
274
334
  # A page number representing "undefined" values, (4294967295).
275
- UNDEFINED_PAGE_NUMBER = 2**32 - 1
335
+ UNDEFINED_PAGE_NUMBER = (2**32) - 1
276
336
 
277
337
  # A helper to check if a page number is the undefined page number.
278
338
  def self.undefined?(page_number)
@@ -285,6 +345,10 @@ module Innodb
285
345
  page_number unless undefined?(page_number)
286
346
  end
287
347
 
348
+ def self.page_type_by_value(value)
349
+ PAGE_TYPE_BY_VALUE[value] || value
350
+ end
351
+
288
352
  # Return the "fil" header from the page, which is common for all page types.
289
353
  def fil_header
290
354
  @fil_header ||= cursor(pos_fil_header).name("fil_header") do |c|
@@ -294,7 +358,7 @@ module Innodb
294
358
  prev: c.name("prev") { Innodb::Page.maybe_undefined(c.read_uint32) },
295
359
  next: c.name("next") { Innodb::Page.maybe_undefined(c.read_uint32) },
296
360
  lsn: c.name("lsn") { c.read_uint64 },
297
- type: c.name("type") { PAGE_TYPE_BY_VALUE[c.read_uint16] },
361
+ type: c.name("type") { Innodb::Page.page_type_by_value(c.read_uint16) },
298
362
  flush_lsn: c.name("flush_lsn") { c.read_uint64 },
299
363
  space_id: c.name("space_id") { c.read_uint32 }
300
364
  )
@@ -405,7 +469,7 @@ module Innodb
405
469
 
406
470
  # Is the page in the doublewrite buffer?
407
471
  def in_doublewrite_buffer?
408
- space&.system_space? && space&.doublewrite_page?(offset)
472
+ space&.system_space? && space.doublewrite_page?(offset)
409
473
  end
410
474
 
411
475
  # Is the space ID stored in the header different from that of the space
data/lib/innodb/record.rb CHANGED
@@ -39,6 +39,16 @@ module Innodb
39
39
  row&.map { |r| "%s=%s" % [r.name, r.value.inspect] }&.join(", ")
40
40
  end
41
41
 
42
+ def full_value_with_externs_for_field(field)
43
+ blob_value = field.value
44
+ extern_page = field.extern && page.space.page(field.extern.page_number)
45
+ while extern_page
46
+ blob_value += extern_page.blob_data
47
+ extern_page = extern_page.next_blob_page
48
+ end
49
+ blob_value
50
+ end
51
+
42
52
  def undo
43
53
  return nil unless roll_pointer
44
54
  return unless (innodb_system = @page.space.innodb_system)
@@ -119,16 +119,15 @@ module Innodb
119
119
 
120
120
  def generate_class(name = "Describer_#{object_id}")
121
121
  str = "class #{name}\n".dup
122
- str << " type %s\n" % [
123
- description[:type].inspect,
124
- ]
122
+ str << format(" type %s\n", description[:type].inspect)
125
123
  %i[key row].each do |group|
126
124
  description[group].each do |item|
127
- str << " %s %s, %s\n" % [
125
+ str << format(
126
+ " %s %s, %s\n",
128
127
  group,
129
128
  item[:name].inspect,
130
- item[:type].map(&:inspect).join(", "),
131
- ]
129
+ item[:type].map(&:inspect).join(", ")
130
+ )
132
131
  end
133
132
  end
134
133
  str << "end\n"
data/lib/innodb/space.rb CHANGED
@@ -276,6 +276,12 @@ module Innodb
276
276
  fsp[:space_id]
277
277
  end
278
278
 
279
+ def checked_page_class!(page, expected_class)
280
+ return page if page.instance_of?(expected_class)
281
+
282
+ raise "Page #{page.offset} is not the correct type, found: #{page.class}, expected: #{expected_class}"
283
+ end
284
+
279
285
  # Return the page number for the space's TRX_SYS page.
280
286
  def page_trx_sys
281
287
  5
@@ -283,13 +289,15 @@ module Innodb
283
289
 
284
290
  # Get the Innodb::Page::TrxSys page for a system space.
285
291
  def trx_sys
286
- page(page_trx_sys) if system_space?
292
+ raise "Transaction System is only available in system spaces" unless system_space?
293
+
294
+ checked_page_class!(page(page_trx_sys), Innodb::Page::TrxSys)
287
295
  end
288
296
 
289
297
  def rseg_page?(page_number)
290
298
  return false unless trx_sys
291
299
 
292
- !trx_sys.rsegs.select { |rseg| rseg.space_id.zero? && rseg.page_number == page_number }.empty?
300
+ trx_sys.rsegs.any? { |rseg| rseg.space_id.zero? && rseg.page_number == page_number }
293
301
  end
294
302
 
295
303
  # Return the page number for the space's SYS data dictionary header.
@@ -299,7 +307,9 @@ module Innodb
299
307
 
300
308
  # Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
301
309
  def data_dictionary_page
302
- page(page_sys_data_dictionary) if system_space?
310
+ raise "Data Dictionary is only available in system spaces" unless system_space?
311
+
312
+ checked_page_class!(page(page_sys_data_dictionary), Innodb::Page::SysDataDictionaryHeader)
303
313
  end
304
314
 
305
315
  # Get an Innodb::List object for a specific list by list name.
@@ -327,7 +337,7 @@ module Innodb
327
337
  # for IBD files, if they haven't added indexes online.
328
338
  (3...@pages).each do |page_number|
329
339
  page = page(page_number)
330
- yield page_number if page.type == :INDEX && page.root?
340
+ yield page_number if page.is_a?(Innodb::Page::Index) && page.root?
331
341
  end
332
342
  end
333
343
 
@@ -358,7 +368,7 @@ module Innodb
358
368
  def each_inode(&block)
359
369
  return enum_for(:each_inode) unless block_given?
360
370
 
361
- each_inode_list.each do |_name, list|
371
+ each_inode_list do |_name, list|
362
372
  list.each do |page|
363
373
  page.each_allocated_inode(&block)
364
374
  end
@@ -238,7 +238,7 @@ module Innodb
238
238
  end
239
239
 
240
240
  def row_string
241
- row&.reject(&:nil?)&.map { |r| r && "%s=%s" % [r[:name], r[:value].inspect] }&.join(", ")
241
+ row&.compact&.map { |r| r && format("%s=%s", r[:name], r[:value].inspect) }&.join(", ")
242
242
  end
243
243
 
244
244
  def string
@@ -76,7 +76,7 @@ class BufferCursor
76
76
  # position, raw byte buffer, and array of names.
77
77
  def print_trace(_cursor, position, bytes, name)
78
78
  slice_size = 16
79
- bytes.each_slice(slice_size).each_with_index do |slice_bytes, slice_count|
79
+ bytes.each_slice(slice_size).with_index do |slice_bytes, slice_count|
80
80
  @trace_io.puts "%06i %s %-32s %s" % [
81
81
  position + (slice_count * slice_size),
82
82
  direction == :backward ? "←" : "→",
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HexFormat
4
+ LINE_SIZE = 16
5
+ GROUP_SIZE = 8
6
+ GROUP_FORMAT_LENGTH = ((LINE_SIZE.to_f / GROUP_SIZE).ceil * (GROUP_SIZE * 3))
7
+
8
+ def self.format_group(data)
9
+ data.map { |n| "%02x" % n.ord }.join(" ")
10
+ end
11
+
12
+ def self.format_groups(data, size)
13
+ data.each_slice(size).map { |g| format_group(g) }.join(" ")
14
+ end
15
+
16
+ def self.format_printable(data)
17
+ data.join.gsub(/[^[:print:]]/, ".")
18
+ end
19
+
20
+ def self.format_hex(data)
21
+ data.chars.each_slice(LINE_SIZE).with_index do |bytes, i|
22
+ yield format("%08i %-#{GROUP_FORMAT_LENGTH}s |%-#{LINE_SIZE}s|",
23
+ (i * LINE_SIZE), format_groups(bytes, GROUP_SIZE), format_printable(bytes))
24
+ end
25
+
26
+ nil
27
+ end
28
+
29
+ def self.puts(data, io: $stdout)
30
+ format_hex(data) { |line| io.puts(line) }
31
+ end
32
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Innodb
4
- VERSION = "0.12.0"
4
+ VERSION = "0.14.0"
5
5
  end
data/lib/innodb/xdes.rb CHANGED
@@ -118,7 +118,7 @@ module Innodb
118
118
  def each_page_status
119
119
  return enum_for(:each_page_status) unless block_given?
120
120
 
121
- bitmap.each_byte.each_with_index do |byte, byte_index|
121
+ bitmap.each_byte.with_index do |byte, byte_index|
122
122
  (0..3).each do |page_offset|
123
123
  page_number = start_page + (byte_index * 4) + page_offset
124
124
  page_bits = ((byte >> (page_offset * BITS_PER_PAGE)) & BITMAP_BV_ALL)
data/lib/innodb.rb CHANGED
@@ -14,10 +14,10 @@ module Innodb
14
14
  end
15
15
  end
16
16
 
17
- require "pp"
18
17
  require "digest/crc32c"
19
18
  require "innodb/util/buffer_cursor"
20
19
  require "innodb/util/read_bits_at_offset"
20
+ require "innodb/util/hex_format"
21
21
 
22
22
  require "innodb/version"
23
23
  require "innodb/stats"
metadata CHANGED
@@ -1,16 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: innodb_ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Cole
8
8
  - Davi Arnaut
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-09-12 00:00:00.000000000 Z
12
+ date: 2024-11-06 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bigdecimal
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 3.1.8
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 3.1.8
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: bindata
16
30
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +65,20 @@ dependencies:
51
65
  - - ">="
52
66
  - !ruby/object:Gem::Version
53
67
  version: 0.4.1
68
+ - !ruby/object:Gem::Dependency
69
+ name: getoptlong
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 0.2.1
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 0.2.1
54
82
  - !ruby/object:Gem::Dependency
55
83
  name: histogram
56
84
  requirement: !ruby/object:Gem::Requirement
@@ -85,14 +113,14 @@ dependencies:
85
113
  requirements:
86
114
  - - "~>"
87
115
  - !ruby/object:Gem::Version
88
- version: 3.10.0
116
+ version: 3.11.0
89
117
  type: :development
90
118
  prerelease: false
91
119
  version_requirements: !ruby/object:Gem::Requirement
92
120
  requirements:
93
121
  - - "~>"
94
122
  - !ruby/object:Gem::Version
95
- version: 3.10.0
123
+ version: 3.11.0
96
124
  - !ruby/object:Gem::Dependency
97
125
  name: rubocop
98
126
  requirement: !ruby/object:Gem::Requirement
@@ -174,14 +202,16 @@ files:
174
202
  - lib/innodb/undo_log.rb
175
203
  - lib/innodb/undo_record.rb
176
204
  - lib/innodb/util/buffer_cursor.rb
205
+ - lib/innodb/util/hex_format.rb
177
206
  - lib/innodb/util/read_bits_at_offset.rb
178
207
  - lib/innodb/version.rb
179
208
  - lib/innodb/xdes.rb
180
209
  homepage: https://github.com/jeremycole/innodb_ruby
181
210
  licenses:
182
211
  - BSD-3-Clause
183
- metadata: {}
184
- post_install_message:
212
+ metadata:
213
+ rubygems_mfa_required: 'false'
214
+ post_install_message:
185
215
  rdoc_options: []
186
216
  require_paths:
187
217
  - lib
@@ -196,8 +226,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
226
  - !ruby/object:Gem::Version
197
227
  version: '0'
198
228
  requirements: []
199
- rubygems_version: 3.0.3
200
- signing_key:
229
+ rubygems_version: 3.5.16
230
+ signing_key:
201
231
  specification_version: 4
202
232
  summary: InnoDB data file parser
203
233
  test_files: []