innodb_ruby 0.9.14 → 0.12.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +5 -6
  3. data/bin/innodb_log +13 -18
  4. data/bin/innodb_space +654 -778
  5. data/lib/innodb/checksum.rb +26 -24
  6. data/lib/innodb/data_dictionary.rb +490 -550
  7. data/lib/innodb/data_type.rb +362 -325
  8. data/lib/innodb/field.rb +102 -89
  9. data/lib/innodb/fseg_entry.rb +22 -26
  10. data/lib/innodb/history.rb +21 -21
  11. data/lib/innodb/history_list.rb +72 -76
  12. data/lib/innodb/ibuf_bitmap.rb +36 -36
  13. data/lib/innodb/ibuf_index.rb +6 -2
  14. data/lib/innodb/index.rb +245 -276
  15. data/lib/innodb/inode.rb +166 -124
  16. data/lib/innodb/list.rb +196 -183
  17. data/lib/innodb/log.rb +139 -110
  18. data/lib/innodb/log_block.rb +100 -91
  19. data/lib/innodb/log_group.rb +53 -64
  20. data/lib/innodb/log_reader.rb +97 -96
  21. data/lib/innodb/log_record.rb +328 -279
  22. data/lib/innodb/lsn.rb +86 -81
  23. data/lib/innodb/page/blob.rb +82 -83
  24. data/lib/innodb/page/fsp_hdr_xdes.rb +174 -165
  25. data/lib/innodb/page/ibuf_bitmap.rb +34 -34
  26. data/lib/innodb/page/index.rb +965 -924
  27. data/lib/innodb/page/index_compressed.rb +34 -34
  28. data/lib/innodb/page/inode.rb +103 -112
  29. data/lib/innodb/page/sys.rb +13 -15
  30. data/lib/innodb/page/sys_data_dictionary_header.rb +81 -59
  31. data/lib/innodb/page/sys_ibuf_header.rb +45 -42
  32. data/lib/innodb/page/sys_rseg_header.rb +88 -82
  33. data/lib/innodb/page/trx_sys.rb +204 -182
  34. data/lib/innodb/page/undo_log.rb +106 -92
  35. data/lib/innodb/page.rb +417 -414
  36. data/lib/innodb/record.rb +121 -164
  37. data/lib/innodb/record_describer.rb +66 -68
  38. data/lib/innodb/space.rb +381 -413
  39. data/lib/innodb/stats.rb +33 -35
  40. data/lib/innodb/system.rb +149 -171
  41. data/lib/innodb/undo_log.rb +129 -107
  42. data/lib/innodb/undo_record.rb +255 -247
  43. data/lib/innodb/util/buffer_cursor.rb +81 -79
  44. data/lib/innodb/util/read_bits_at_offset.rb +2 -1
  45. data/lib/innodb/version.rb +2 -2
  46. data/lib/innodb/xdes.rb +144 -142
  47. data/lib/innodb.rb +4 -5
  48. metadata +100 -25
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