innodb_ruby 0.12.0 → 0.14.0

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.
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: []