innodb_ruby 0.9.13 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +14 -19
  4. data/bin/innodb_space +592 -745
  5. data/lib/innodb.rb +5 -5
  6. data/lib/innodb/checksum.rb +26 -24
  7. data/lib/innodb/data_dictionary.rb +490 -550
  8. data/lib/innodb/data_type.rb +362 -325
  9. data/lib/innodb/field.rb +102 -89
  10. data/lib/innodb/fseg_entry.rb +22 -26
  11. data/lib/innodb/history.rb +21 -21
  12. data/lib/innodb/history_list.rb +72 -76
  13. data/lib/innodb/ibuf_bitmap.rb +36 -36
  14. data/lib/innodb/ibuf_index.rb +6 -2
  15. data/lib/innodb/index.rb +245 -275
  16. data/lib/innodb/inode.rb +166 -124
  17. data/lib/innodb/list.rb +191 -183
  18. data/lib/innodb/log.rb +139 -110
  19. data/lib/innodb/log_block.rb +100 -91
  20. data/lib/innodb/log_group.rb +53 -64
  21. data/lib/innodb/log_reader.rb +97 -96
  22. data/lib/innodb/log_record.rb +328 -279
  23. data/lib/innodb/lsn.rb +86 -81
  24. data/lib/innodb/page.rb +446 -291
  25. data/lib/innodb/page/blob.rb +82 -83
  26. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  27. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  28. data/lib/innodb/page/index.rb +965 -924
  29. data/lib/innodb/page/index_compressed.rb +34 -34
  30. data/lib/innodb/page/inode.rb +103 -112
  31. data/lib/innodb/page/sys.rb +13 -15
  32. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  33. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  34. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  35. data/lib/innodb/page/trx_sys.rb +204 -182
  36. data/lib/innodb/page/undo_log.rb +106 -92
  37. data/lib/innodb/record.rb +121 -164
  38. data/lib/innodb/record_describer.rb +66 -68
  39. data/lib/innodb/space.rb +386 -391
  40. data/lib/innodb/stats.rb +33 -35
  41. data/lib/innodb/system.rb +149 -171
  42. data/lib/innodb/undo_log.rb +129 -107
  43. data/lib/innodb/undo_record.rb +255 -247
  44. data/lib/innodb/util/buffer_cursor.rb +81 -79
  45. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  46. data/lib/innodb/version.rb +2 -2
  47. data/lib/innodb/xdes.rb +144 -142
  48. metadata +112 -21
data/lib/innodb/log.rb CHANGED
@@ -1,126 +1,155 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  # An InnoDB transaction log file.
4
- class Innodb::Log
5
- # A map of the name and position of the blocks that form the log header.
6
- LOG_HEADER_BLOCK_MAP = {
7
- :LOG_FILE_HEADER => 0,
8
- :LOG_CHECKPOINT_1 => 1,
9
- :EMPTY => 2,
10
- :LOG_CHECKPOINT_2 => 3,
11
- }
12
-
13
- # Number of blocks in the log file header.
14
- LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
15
-
16
- # The size in bytes of the log file header.
17
- LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
18
-
19
- # Maximum number of log group checkpoints.
20
- LOG_CHECKPOINT_GROUPS = 32
21
-
22
- # Open a log file.
23
- def initialize(filename)
24
- @file = File.open(filename)
25
- @size = @file.stat.size
26
- @blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
27
- @capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
28
- end
4
+ module Innodb
5
+ class Log
6
+ Header = Struct.new(
7
+ :group_id,
8
+ :start_lsn,
9
+ :file_no,
10
+ :created_by,
11
+ keyword_init: true
12
+ )
29
13
 
30
- # The size (in bytes) of the log.
31
- attr_reader :size
14
+ Checkpoint = Struct.new(
15
+ :number,
16
+ :lsn,
17
+ :lsn_offset,
18
+ :buffer_size,
19
+ :archived_lsn,
20
+ :group_array,
21
+ :checksum_1,
22
+ :checksum_2,
23
+ :fsp_free_limit,
24
+ :fsp_magic,
25
+ keyword_init: true
26
+ )
32
27
 
33
- # The log capacity (in bytes).
34
- attr_reader :capacity
28
+ CheckpointGroup = Struct.new(
29
+ :archived_file_no,
30
+ :archived_offset,
31
+ keyword_init: true
32
+ )
35
33
 
36
- # The number of blocks in the log.
37
- attr_reader :blocks
34
+ CheckpointSet = Struct.new(
35
+ :checkpoint_1,
36
+ :checkpoint_2,
37
+ keyword_init: true
38
+ )
38
39
 
39
- # Get the raw byte buffer for a specific block by block offset.
40
- def block_data(offset)
41
- raise "Invalid block offset" unless (offset % Innodb::LogBlock::BLOCK_SIZE).zero?
42
- @file.sysseek(offset)
43
- @file.sysread(Innodb::LogBlock::BLOCK_SIZE)
44
- end
40
+ # A map of the name and position of the blocks that form the log header.
41
+ LOG_HEADER_BLOCK_MAP = {
42
+ LOG_FILE_HEADER: 0,
43
+ LOG_CHECKPOINT_1: 1,
44
+ EMPTY: 2,
45
+ LOG_CHECKPOINT_2: 3,
46
+ }.freeze
45
47
 
46
- # Get a cursor to a block in a given offset of the log.
47
- def block_cursor(offset)
48
- BufferCursor.new(block_data(offset), 0)
49
- end
48
+ # Number of blocks in the log file header.
49
+ LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
50
50
 
51
- # Return the log header.
52
- def header
53
- offset = LOG_HEADER_BLOCK_MAP[:LOG_FILE_HEADER] * Innodb::LogBlock::BLOCK_SIZE
54
- @header ||= block_cursor(offset).name("header") do |c|
55
- {
56
- :group_id => c.name("group_id") { c.get_uint32 },
57
- :start_lsn => c.name("start_lsn") { c.get_uint64 },
58
- :file_no => c.name("file_no") { c.get_uint32 },
59
- :created_by => c.name("created_by") { c.get_string(32) }
60
- }
51
+ # The size in bytes of the log file header.
52
+ LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
53
+
54
+ # Maximum number of log group checkpoints.
55
+ LOG_CHECKPOINT_GROUPS = 32
56
+
57
+ # Open a log file.
58
+ def initialize(filename)
59
+ @file = File.open(filename)
60
+ @size = @file.stat.size
61
+ @blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
62
+ @capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
61
63
  end
62
- end
63
64
 
64
- # Read a log checkpoint from the given cursor.
65
- def read_checkpoint(c)
66
- # Log archive related fields (e.g. group_array) are not currently in
67
- # use or even read by InnoDB. However, for the sake of completeness,
68
- # they are included.
69
- {
70
- :number => c.name("number") { c.get_uint64 },
71
- :lsn => c.name("lsn") { c.get_uint64 },
72
- :lsn_offset => c.name("lsn_offset") { c.get_uint32 },
73
- :buffer_size => c.name("buffer_size") { c.get_uint32 },
74
- :archived_lsn => c.name("archived_lsn") { c.get_uint64 },
75
- :group_array =>
76
- (0 .. LOG_CHECKPOINT_GROUPS - 1).map do |n|
77
- c.name("group_array[#{n}]") do
78
- {
79
- :archived_file_no => c.name("archived_file_no") { c.get_uint32 },
80
- :archived_offset => c.name("archived_offset") { c.get_uint32 },
81
- }
82
- end
83
- end,
84
- :checksum_1 => c.name("checksum_1") { c.get_uint32 },
85
- :checksum_2 => c.name("checksum_2") { c.get_uint32 },
86
- :fsp_free_limit => c.name("fsp_free_limit") { c.get_uint32 },
87
- :fsp_magic => c.name("fsp_magic") { c.get_uint32 },
88
- }
89
- end
65
+ # The size (in bytes) of the log.
66
+ attr_reader :size
90
67
 
91
- # Return the log checkpoints.
92
- def checkpoint
93
- offset1 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_1] * Innodb::LogBlock::BLOCK_SIZE
94
- offset2 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_2] * Innodb::LogBlock::BLOCK_SIZE
95
- @checkpoint ||=
96
- {
97
- :checkpoint_1 => block_cursor(offset1).name("checkpoint_1") do |cursor|
98
- cp = read_checkpoint(cursor)
99
- cp.delete(:group_array)
100
- cp
101
- end,
102
- :checkpoint_2 => block_cursor(offset2).name("checkpoint_2") do |cursor|
103
- cp = read_checkpoint(cursor)
104
- cp.delete(:group_array)
105
- cp
106
- end
107
- }
108
- end
68
+ # The log capacity (in bytes).
69
+ attr_reader :capacity
109
70
 
110
- # Return a log block with a given block index as an InnoDB::LogBlock object.
111
- # Blocks are indexed after the log file header, starting from 0.
112
- def block(block_index)
113
- return nil unless block_index.between?(0, @blocks - 1)
114
- offset = (LOG_HEADER_BLOCKS + block_index.to_i) * Innodb::LogBlock::BLOCK_SIZE
115
- Innodb::LogBlock.new(block_data(offset))
116
- end
71
+ # The number of blocks in the log.
72
+ attr_reader :blocks
73
+
74
+ # Get the raw byte buffer for a specific block by block offset.
75
+ def block_data(offset)
76
+ raise "Invalid block offset" unless (offset % Innodb::LogBlock::BLOCK_SIZE).zero?
77
+
78
+ @file.sysseek(offset)
79
+ @file.sysread(Innodb::LogBlock::BLOCK_SIZE)
80
+ end
81
+
82
+ # Get a cursor to a block in a given offset of the log.
83
+ def block_cursor(offset)
84
+ BufferCursor.new(block_data(offset), 0)
85
+ end
86
+
87
+ # Return the log header.
88
+ def header
89
+ offset = LOG_HEADER_BLOCK_MAP[:LOG_FILE_HEADER] * Innodb::LogBlock::BLOCK_SIZE
90
+ @header ||= block_cursor(offset).name("header") do |c|
91
+ Header.new(
92
+ group_id: c.name("group_id") { c.read_uint32 },
93
+ start_lsn: c.name("start_lsn") { c.read_uint64 },
94
+ file_no: c.name("file_no") { c.read_uint32 },
95
+ created_by: c.name("created_by") { c.read_string(32) }
96
+ )
97
+ end
98
+ end
99
+
100
+ # Read a log checkpoint from the given cursor.
101
+ def read_checkpoint(cursor)
102
+ # Log archive related fields (e.g. group_array) are not currently in
103
+ # use or even read by InnoDB. However, for the sake of completeness,
104
+ # they are included.
105
+ Checkpoint.new(
106
+ number: cursor.name("number") { cursor.read_uint64 },
107
+ lsn: cursor.name("lsn") { cursor.read_uint64 },
108
+ lsn_offset: cursor.name("lsn_offset") { cursor.read_uint32 },
109
+ buffer_size: cursor.name("buffer_size") { cursor.read_uint32 },
110
+ archived_lsn: cursor.name("archived_lsn") { cursor.read_uint64 },
111
+ group_array:
112
+ (0..(LOG_CHECKPOINT_GROUPS - 1)).map do |n|
113
+ cursor.name("group_array[#{n}]") do
114
+ CheckpointGroup.new(
115
+ archived_file_no: cursor.name("archived_file_no") { cursor.read_uint32 },
116
+ archived_offset: cursor.name("archived_offset") { cursor.read_uint32 }
117
+ )
118
+ end
119
+ end,
120
+ checksum_1: cursor.name("checksum_1") { cursor.read_uint32 },
121
+ checksum_2: cursor.name("checksum_2") { cursor.read_uint32 },
122
+ fsp_free_limit: cursor.name("fsp_free_limit") { cursor.read_uint32 },
123
+ fsp_magic: cursor.name("fsp_magic") { cursor.read_uint32 }
124
+ )
125
+ end
126
+
127
+ # Return the log checkpoints.
128
+ def checkpoint
129
+ offset1 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_1] * Innodb::LogBlock::BLOCK_SIZE
130
+ offset2 = LOG_HEADER_BLOCK_MAP[:LOG_CHECKPOINT_2] * Innodb::LogBlock::BLOCK_SIZE
131
+ @checkpoint ||= CheckpointSet.new(
132
+ checkpoint_1: block_cursor(offset1).name("checkpoint_1") { |c| read_checkpoint(c) },
133
+ checkpoint_2: block_cursor(offset2).name("checkpoint_2") { |c| read_checkpoint(c) }
134
+ )
135
+ end
136
+
137
+ # Return a log block with a given block index as an InnoDB::LogBlock object.
138
+ # Blocks are indexed after the log file header, starting from 0.
139
+ def block(block_index)
140
+ return nil unless block_index.between?(0, @blocks - 1)
141
+
142
+ offset = (LOG_HEADER_BLOCKS + block_index.to_i) * Innodb::LogBlock::BLOCK_SIZE
143
+ Innodb::LogBlock.new(block_data(offset))
144
+ end
117
145
 
118
- # Iterate through all log blocks, returning the block index and an
119
- # InnoDB::LogBlock object for each block.
120
- def each_block
121
- (0...@blocks).each do |block_index|
122
- current_block = block(block_index)
123
- yield block_index, current_block if current_block
146
+ # Iterate through all log blocks, returning the block index and an
147
+ # InnoDB::LogBlock object for each block.
148
+ def each_block
149
+ (0...@blocks).each do |block_index|
150
+ current_block = block(block_index)
151
+ yield block_index, current_block if current_block
152
+ end
124
153
  end
125
154
  end
126
155
  end
@@ -1,120 +1,129 @@
1
- # -*- encoding : utf-8 -*-
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
2
4
 
3
5
  # An InnoDB transaction log block.
4
- class Innodb::LogBlock
5
- # Log blocks are fixed-length at 512 bytes in InnoDB.
6
- BLOCK_SIZE = 512
6
+ module Innodb
7
+ class LogBlock
8
+ extend Forwardable
7
9
 
8
- # Offset of the header within the log block.
9
- HEADER_OFFSET = 0
10
+ Header = Struct.new(
11
+ :flush,
12
+ :block_number,
13
+ :data_length,
14
+ :first_rec_group,
15
+ :checkpoint_no,
16
+ keyword_init: true
17
+ )
10
18
 
11
- # The size of the block header.
12
- HEADER_SIZE = 4 + 2 + 2 + 4
19
+ Trailer = Struct.new(
20
+ :checksum,
21
+ keyword_init: true
22
+ )
13
23
 
14
- # Offset of the trailer within ths log block.
15
- TRAILER_OFFSET = BLOCK_SIZE - 4
24
+ # Log blocks are fixed-length at 512 bytes in InnoDB.
25
+ BLOCK_SIZE = 512
16
26
 
17
- # The size of the block trailer.
18
- TRAILER_SIZE = 4
27
+ # Offset of the header within the log block.
28
+ HEADER_OFFSET = 0
19
29
 
20
- # Offset of the start of data in the block.
21
- DATA_OFFSET = HEADER_SIZE
30
+ # The size of the block header.
31
+ HEADER_SIZE = 4 + 2 + 2 + 4
22
32
 
23
- # Size of the space available for log records.
24
- DATA_SIZE = BLOCK_SIZE - HEADER_SIZE - TRAILER_SIZE
33
+ # Offset of the trailer within ths log block.
34
+ TRAILER_OFFSET = BLOCK_SIZE - 4
25
35
 
26
- # Mask used to get the flush bit in the header.
27
- HEADER_FLUSH_BIT_MASK = 0x80000000
36
+ # The size of the block trailer.
37
+ TRAILER_SIZE = 4
28
38
 
29
- # Initialize a log block by passing in a 512-byte buffer containing the raw
30
- # log block contents.
31
- def initialize(buffer)
32
- unless buffer.size == BLOCK_SIZE
33
- raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
34
- end
39
+ # Offset of the start of data in the block.
40
+ DATA_OFFSET = HEADER_SIZE
35
41
 
36
- @buffer = buffer
37
- end
42
+ # Size of the space available for log records.
43
+ DATA_SIZE = BLOCK_SIZE - HEADER_SIZE - TRAILER_SIZE
38
44
 
39
- # Return an BufferCursor object positioned at a specific offset.
40
- def cursor(offset)
41
- BufferCursor.new(@buffer, offset)
42
- end
45
+ # Mask used to get the flush bit in the header.
46
+ HEADER_FLUSH_BIT_MASK = 0x80000000
43
47
 
44
- # Return the log block header.
45
- def header
46
- @header ||= cursor(HEADER_OFFSET).name("header") do |c|
47
- {
48
- :flush => c.name("flush") {
49
- c.peek { (c.get_uint32 & HEADER_FLUSH_BIT_MASK) > 0 }
50
- },
51
- :block_number => c.name("block_number") {
52
- c.get_uint32 & ~HEADER_FLUSH_BIT_MASK
53
- },
54
- :data_length => c.name("data_length") { c.get_uint16 },
55
- :first_rec_group => c.name("first_rec_group") { c.get_uint16 },
56
- :checkpoint_no => c.name("checkpoint_no") { c.get_uint32 },
57
- }
58
- end
59
- end
48
+ # Initialize a log block by passing in a 512-byte buffer containing the raw
49
+ # log block contents.
50
+ def initialize(buffer)
51
+ raise "Log block buffer provided was not #{BLOCK_SIZE} bytes" unless buffer.size == BLOCK_SIZE
60
52
 
61
- # Return a slice of actual block data (that is, excluding header and
62
- # trailer) starting at the given offset.
63
- def data(offset = DATA_OFFSET)
64
- length = header[:data_length]
53
+ @buffer = buffer
54
+ end
65
55
 
66
- if length == BLOCK_SIZE
67
- length -= TRAILER_SIZE
56
+ # Return an BufferCursor object positioned at a specific offset.
57
+ def cursor(offset)
58
+ BufferCursor.new(@buffer, offset)
68
59
  end
69
60
 
70
- if offset < DATA_OFFSET || offset > length
71
- raise "Invalid block data offset"
61
+ # Return the log block header.
62
+ def header
63
+ @header ||= cursor(HEADER_OFFSET).name("header") do |c|
64
+ Header.new(
65
+ flush: c.name("flush") { c.peek { (c.read_uint32 & HEADER_FLUSH_BIT_MASK).positive? } },
66
+ block_number: c.name("block_number") { c.read_uint32 & ~HEADER_FLUSH_BIT_MASK },
67
+ data_length: c.name("data_length") { c.read_uint16 },
68
+ first_rec_group: c.name("first_rec_group") { c.read_uint16 },
69
+ checkpoint_no: c.name("checkpoint_no") { c.read_uint32 }
70
+ )
71
+ end
72
72
  end
73
73
 
74
- @buffer.slice(offset, length - offset)
75
- end
74
+ def_delegator :header, :flush
75
+ def_delegator :header, :block_number
76
+ def_delegator :header, :data_length
77
+ def_delegator :header, :first_rec_group
78
+ def_delegator :header, :checkpoint_no
79
+
80
+ # Return a slice of actual block data (that is, excluding header and
81
+ # trailer) starting at the given offset.
82
+ def data(offset = DATA_OFFSET)
83
+ length = data_length
84
+ length -= TRAILER_SIZE if length == BLOCK_SIZE
76
85
 
77
- # Return the log block trailer.
78
- def trailer
79
- @trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
80
- {
81
- :checksum => c.name("checksum") { c.get_uint32 },
82
- }
86
+ raise "Invalid block data offset" if offset < DATA_OFFSET || offset > length
87
+
88
+ @buffer.slice(offset, length - offset)
83
89
  end
84
- end
85
90
 
86
- # A helper function to return the checksum from the trailer, for
87
- # easier access.
88
- def checksum
89
- trailer[:checksum]
90
- end
91
+ # Return the log block trailer.
92
+ def trailer
93
+ @trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
94
+ Trailer.new(checksum: c.name("checksum") { c.read_uint32 })
95
+ end
96
+ end
91
97
 
92
- # Calculate the checksum of the block using InnoDB's log block
93
- # checksum algorithm.
94
- def calculate_checksum
95
- cksum = 1
96
- shift = (0..24).cycle
97
- cursor(0).each_byte_as_uint8(TRAILER_OFFSET) do |b|
98
- cksum &= 0x7fffffff
99
- cksum += b + (b << shift.next)
98
+ def_delegator :trailer, :checksum
99
+
100
+ # Calculate the checksum of the block using InnoDB's log block
101
+ # checksum algorithm.
102
+ def calculate_checksum
103
+ csum = 1
104
+ shift = (0..24).cycle
105
+ cursor(0).each_byte_as_uint8(TRAILER_OFFSET) do |b|
106
+ csum &= 0x7fffffff
107
+ csum += b + (b << shift.next)
108
+ end
109
+ csum
100
110
  end
101
- cksum
102
- end
103
111
 
104
- # Is the block corrupt? Calculate the checksum of the block and compare to
105
- # the stored checksum; return true or false.
106
- def corrupt?
107
- checksum != calculate_checksum
108
- end
112
+ # Is the block corrupt? Calculate the checksum of the block and compare to
113
+ # the stored checksum; return true or false.
114
+ def corrupt?
115
+ checksum != calculate_checksum
116
+ end
109
117
 
110
- # Dump the contents of a log block for debugging purposes.
111
- def dump
112
- puts
113
- puts "header:"
114
- pp header
118
+ # Dump the contents of a log block for debugging purposes.
119
+ def dump
120
+ puts
121
+ puts "header:"
122
+ pp header
115
123
 
116
- puts
117
- puts "trailer:"
118
- pp trailer
124
+ puts
125
+ puts "trailer:"
126
+ pp trailer
127
+ end
119
128
  end
120
129
  end