innodb_ruby 0.9.0 → 0.9.5

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.
@@ -29,6 +29,7 @@ class Innodb::Inode
29
29
  # Construct a new Inode by reading an FSEG header from a cursor.
30
30
  def self.new_from_cursor(space, cursor)
31
31
  data = {
32
+ :offset => cursor.position,
32
33
  :fseg_id => cursor.name("fseg_id") {
33
34
  cursor.get_uint64
34
35
  },
@@ -62,6 +63,7 @@ class Innodb::Inode
62
63
  @data = data
63
64
  end
64
65
 
66
+ def offset; @data[:offset]; end
65
67
  def fseg_id; @data[:fseg_id]; end
66
68
  def not_full_n_used; @data[:not_full_n_used]; end
67
69
  def free; @data[:free]; end
@@ -70,6 +72,14 @@ class Innodb::Inode
70
72
  def magic_n; @data[:magic_n]; end
71
73
  def frag_array; @data[:frag_array]; end
72
74
 
75
+ def inspect
76
+ "<%s space=%s, fseg=%i>" % [
77
+ self.class.name,
78
+ space.inspect,
79
+ fseg_id,
80
+ ]
81
+ end
82
+
73
83
  # Helper method to determine if an Inode is in use. Inodes that are not in
74
84
  # use have an fseg_id of 0.
75
85
  def allocated?
@@ -124,7 +134,7 @@ class Innodb::Inode
124
134
 
125
135
  # Compare one Innodb::Inode to another.
126
136
  def ==(other)
127
- fseg_id == other.fseg_id
137
+ fseg_id == other.fseg_id if other
128
138
  end
129
139
 
130
140
  # Dump a summary of this object for debugging purposes.
@@ -109,8 +109,8 @@ class Innodb::List
109
109
  end
110
110
 
111
111
  # Return a list cursor for the list.
112
- def list_cursor(node=nil)
113
- ListCursor.new(self, node)
112
+ def list_cursor(node=:min, direction=:forward)
113
+ ListCursor.new(self, node, direction)
114
114
  end
115
115
 
116
116
  # Return whether the given item is present in the list. This depends on the
@@ -127,36 +127,50 @@ class Innodb::List
127
127
  return enum_for(:each)
128
128
  end
129
129
 
130
- c = list_cursor
131
- while e = c.next
132
- yield e
130
+ list_cursor.each_node do |node|
131
+ yield node
133
132
  end
134
133
  end
135
134
 
136
135
  # A list iteration cursor used primarily by the Innodb::List #cursor method
137
136
  # implicitly. Keeps its own state for iterating through lists efficiently.
138
137
  class ListCursor
139
- def initialize(list, node=nil)
140
- @list = list
141
- @cursor = node
138
+ def initialize(list, node=:min, direction=:forward)
139
+ @initial = true
140
+ @list = list
141
+ @direction = direction
142
+
143
+ case node
144
+ when :min
145
+ @node = @list.first
146
+ when :max
147
+ @node = @list.last
148
+ else
149
+ @node = node
150
+ end
142
151
  end
143
152
 
144
- # Reset the list cursor to its default starting state, which will allow
145
- # iteration forwards from the first entry (using #next) or backwards
146
- # from the last entry (using #prev).
147
- def reset
148
- @cursor = nil
153
+ def node
154
+ if @initial
155
+ @initial = false
156
+ return @node
157
+ end
158
+
159
+ case @direction
160
+ when :forward
161
+ next_node
162
+ when :backward
163
+ prev_node
164
+ end
149
165
  end
150
166
 
151
167
  # Return the previous entry from the current position, and advance the
152
168
  # cursor position to the returned entry. If the cursor is currently nil,
153
169
  # return the last entry in the list and adjust the cursor position to
154
170
  # that entry.
155
- def prev
156
- if @cursor
157
- @cursor = @list.prev(@cursor)
158
- else
159
- @cursor = @list.last
171
+ def prev_node
172
+ if node = @list.prev(@node)
173
+ @node = node
160
174
  end
161
175
  end
162
176
 
@@ -164,11 +178,19 @@ class Innodb::List
164
178
  # cursor position to the returned entry. If the cursor is currently nil,
165
179
  # return the first entry in the list and adjust the cursor position to
166
180
  # that entry.
167
- def next
168
- if @cursor
169
- @cursor = @list.next(@cursor)
170
- else
171
- @cursor = @list.first
181
+ def next_node
182
+ if node = @list.next(@node)
183
+ @node = node
184
+ end
185
+ end
186
+
187
+ def each_node
188
+ unless block_given?
189
+ return enum_for(:each_node)
190
+ end
191
+
192
+ while n = node
193
+ yield n
172
194
  end
173
195
  end
174
196
  end
@@ -1,7 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  # An InnoDB transaction log file.
4
-
5
4
  class Innodb::Log
6
5
  # A map of the name and position of the blocks that form the log header.
7
6
  LOG_HEADER_BLOCK_MAP = {
@@ -14,32 +13,39 @@ class Innodb::Log
14
13
  # Number of blocks in the log file header.
15
14
  LOG_HEADER_BLOCKS = LOG_HEADER_BLOCK_MAP.size
16
15
 
16
+ # The size in bytes of the log file header.
17
+ LOG_HEADER_SIZE = LOG_HEADER_BLOCKS * Innodb::LogBlock::BLOCK_SIZE
18
+
17
19
  # Maximum number of log group checkpoints.
18
20
  LOG_CHECKPOINT_GROUPS = 32
19
21
 
20
22
  # Open a log file.
21
- def initialize(file)
22
- @file = File.open(file)
23
+ def initialize(filename)
24
+ @file = File.open(filename)
23
25
  @size = @file.stat.size
24
26
  @blocks = (@size / Innodb::LogBlock::BLOCK_SIZE) - LOG_HEADER_BLOCKS
27
+ @capacity = @blocks * Innodb::LogBlock::BLOCK_SIZE
25
28
  end
26
29
 
27
30
  # The size (in bytes) of the log.
28
31
  attr_reader :size
29
32
 
30
- # The number of blocks in the the log.
33
+ # The log capacity (in bytes).
34
+ attr_reader :capacity
35
+
36
+ # The number of blocks in the log.
31
37
  attr_reader :blocks
32
38
 
33
39
  # Get the raw byte buffer for a specific block by block offset.
34
40
  def block_data(offset)
35
41
  raise "Invalid block offset" unless (offset % Innodb::LogBlock::BLOCK_SIZE).zero?
36
- @file.seek(offset)
37
- @file.read(Innodb::LogBlock::BLOCK_SIZE)
42
+ @file.sysseek(offset)
43
+ @file.sysread(Innodb::LogBlock::BLOCK_SIZE)
38
44
  end
39
45
 
40
46
  # Get a cursor to a block in a given offset of the log.
41
47
  def block_cursor(offset)
42
- Innodb::Cursor.new(block_data(offset), 0)
48
+ BufferCursor.new(block_data(offset), 0)
43
49
  end
44
50
 
45
51
  # Return the log header.
@@ -49,7 +55,8 @@ class Innodb::Log
49
55
  {
50
56
  :group_id => c.name("group_id") { c.get_uint32 },
51
57
  :start_lsn => c.name("start_lsn") { c.get_uint64 },
52
- :created_by => c.name("created_by") { c.seek(16).get_bytes(4) }
58
+ :file_no => c.name("file_no") { c.get_uint32 },
59
+ :created_by => c.name("created_by") { c.get_string(32) }
53
60
  }
54
61
  end
55
62
  end
@@ -62,7 +69,7 @@ class Innodb::Log
62
69
  {
63
70
  :number => c.name("number") { c.get_uint64 },
64
71
  :lsn => c.name("lsn") { c.get_uint64 },
65
- :offset => c.name("offset") { c.get_uint32 },
72
+ :lsn_offset => c.name("lsn_offset") { c.get_uint32 },
66
73
  :buffer_size => c.name("buffer_size") { c.get_uint32 },
67
74
  :archived_lsn => c.name("archived_lsn") { c.get_uint64 },
68
75
  :group_array =>
@@ -88,10 +95,14 @@ class Innodb::Log
88
95
  @checkpoint ||=
89
96
  {
90
97
  :checkpoint_1 => block_cursor(offset1).name("checkpoint_1") do |cursor|
91
- read_checkpoint(cursor)
98
+ cp = read_checkpoint(cursor)
99
+ cp.delete(:group_array)
100
+ cp
92
101
  end,
93
102
  :checkpoint_2 => block_cursor(offset2).name("checkpoint_2") do |cursor|
94
- read_checkpoint(cursor)
103
+ cp = read_checkpoint(cursor)
104
+ cp.delete(:group_array)
105
+ cp
95
106
  end
96
107
  }
97
108
  end
@@ -1,8 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- require "innodb/cursor"
4
- require "pp"
5
-
6
3
  # An InnoDB transaction log block.
7
4
  class Innodb::LogBlock
8
5
  # Log blocks are fixed-length at 512 bytes in InnoDB.
@@ -11,9 +8,21 @@ class Innodb::LogBlock
11
8
  # Offset of the header within the log block.
12
9
  HEADER_OFFSET = 0
13
10
 
11
+ # The size of the block header.
12
+ HEADER_SIZE = 4 + 2 + 2 + 4
13
+
14
14
  # Offset of the trailer within ths log block.
15
15
  TRAILER_OFFSET = BLOCK_SIZE - 4
16
16
 
17
+ # The size of the block trailer.
18
+ TRAILER_SIZE = 4
19
+
20
+ # Offset of the start of data in the block.
21
+ DATA_OFFSET = HEADER_SIZE
22
+
23
+ # Size of the space available for log records.
24
+ DATA_SIZE = BLOCK_SIZE - HEADER_SIZE - TRAILER_SIZE
25
+
17
26
  # Mask used to get the flush bit in the header.
18
27
  HEADER_FLUSH_BIT_MASK = 0x80000000
19
28
 
@@ -21,15 +30,15 @@ class Innodb::LogBlock
21
30
  # log block contents.
22
31
  def initialize(buffer)
23
32
  unless buffer.size == BLOCK_SIZE
24
- raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
33
+ raise "Log block buffer provided was not #{BLOCK_SIZE} bytes"
25
34
  end
26
35
 
27
36
  @buffer = buffer
28
37
  end
29
38
 
30
- # Return an Innodb::Cursor object positioned at a specific offset.
39
+ # Return an BufferCursor object positioned at a specific offset.
31
40
  def cursor(offset)
32
- Innodb::Cursor.new(@buffer, offset)
41
+ BufferCursor.new(@buffer, offset)
33
42
  end
34
43
 
35
44
  # Return the log block header.
@@ -49,6 +58,22 @@ class Innodb::LogBlock
49
58
  end
50
59
  end
51
60
 
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]
65
+
66
+ if length == BLOCK_SIZE
67
+ length -= TRAILER_SIZE
68
+ end
69
+
70
+ if offset < DATA_OFFSET || offset > length
71
+ raise "Invalid block data offset"
72
+ end
73
+
74
+ @buffer.slice(offset, length - offset)
75
+ end
76
+
52
77
  # Return the log block trailer.
53
78
  def trailer
54
79
  @trailer ||= cursor(TRAILER_OFFSET).name("trailer") do |c|
@@ -58,79 +83,28 @@ class Innodb::LogBlock
58
83
  end
59
84
  end
60
85
 
61
- # The constants used by InnoDB for identifying different log record types.
62
- RECORD_TYPES = {
63
- 1 => :MLOG_1BYTE,
64
- 2 => :MLOG_2BYTE,
65
- 4 => :MLOG_4BYTE,
66
- 8 => :MLOG_8BYTE,
67
- 9 => :REC_INSERT,
68
- 10 => :REC_CLUST_DELETE_MARK,
69
- 11 => :REC_SEC_DELETE_MARK,
70
- 13 => :REC_UPDATE_IN_PLACE,
71
- 14 => :REC_DELETE,
72
- 15 => :LIST_END_DELETE,
73
- 16 => :LIST_START_DELETE,
74
- 17 => :LIST_END_COPY_CREATED,
75
- 18 => :PAGE_REORGANIZE,
76
- 19 => :PAGE_CREATE,
77
- 20 => :UNDO_INSERT,
78
- 21 => :UNDO_ERASE_END,
79
- 22 => :UNDO_INIT,
80
- 23 => :UNDO_HDR_DISCARD,
81
- 24 => :UNDO_HDR_REUSE,
82
- 25 => :UNDO_HDR_CREATE,
83
- 26 => :REC_MIN_MARK,
84
- 27 => :IBUF_BITMAP_INIT,
85
- 28 => :LSN,
86
- 29 => :INIT_FILE_PAGE,
87
- 30 => :WRITE_STRING,
88
- 31 => :MULTI_REC_END,
89
- 32 => :DUMMY_RECORD,
90
- 33 => :FILE_CREATE,
91
- 34 => :FILE_RENAME,
92
- 35 => :FILE_DELETE,
93
- 36 => :COMP_REC_MIN_MARK,
94
- 37 => :COMP_PAGE_CREATE,
95
- 38 => :COMP_REC_INSERT,
96
- 39 => :COMP_REC_CLUST_DELETE_MARK,
97
- 40 => :COMP_REC_SEC_DELETE_MARK,
98
- 41 => :COMP_REC_UPDATE_IN_PLACE,
99
- 42 => :COMP_REC_DELETE,
100
- 43 => :COMP_LIST_END_DELETE,
101
- 44 => :COMP_LIST_START_DELETE,
102
- 45 => :COMP_LIST_END_COPY_CREATE,
103
- 46 => :COMP_PAGE_REORGANIZE,
104
- 47 => :FILE_CREATE2,
105
- 48 => :ZIP_WRITE_NODE_PTR,
106
- 49 => :ZIP_WRITE_BLOB_PTR,
107
- 50 => :ZIP_WRITE_HEADER,
108
- 51 => :ZIP_PAGE_COMPRESS,
109
- }
110
-
111
- SINGLE_RECORD_MASK = 0x80
112
- RECORD_TYPE_MASK = 0x7f
113
-
114
- # Return a preamble of the first record in this block.
115
- def first_record_preamble
116
- return nil unless header[:first_rec_group] > 0
117
- cursor(header[:first_rec_group]).name("header") do |c|
118
- type_and_flag = c.name("type") { c.get_uint8 }
119
- type = type_and_flag & RECORD_TYPE_MASK
120
- type = RECORD_TYPES[type] || type
121
- single_record = (type_and_flag & SINGLE_RECORD_MASK) > 0
122
- case type
123
- when :MULTI_REC_END, :DUMMY_RECORD
124
- { :type => type }
125
- else
126
- {
127
- :type => type,
128
- :single_record => single_record,
129
- :space => c.name("space") { c.get_ic_uint32 },
130
- :page_number => c.name("page_number") { c.get_ic_uint32 },
131
- }
132
- end
86
+ # A helper function to return the checksum from the trailer, for
87
+ # easier access.
88
+ def checksum
89
+ trailer[:checksum]
90
+ end
91
+
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)
133
100
  end
101
+ cksum
102
+ end
103
+
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
134
108
  end
135
109
 
136
110
  # Dump the contents of a log block for debugging purposes.
@@ -142,9 +116,5 @@ class Innodb::LogBlock
142
116
  puts
143
117
  puts "trailer:"
144
118
  pp trailer
145
-
146
- puts
147
- puts "record:"
148
- pp first_record_preamble
149
119
  end
150
120
  end
@@ -1,79 +1,84 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
+ # Group of InnoDB logs files that make up the redo log.
3
4
  class Innodb::LogGroup
4
- def initialize
5
- @files = []
5
+
6
+ # Initialize group given a set of sorted log files.
7
+ def initialize(log_files)
8
+ @logs = log_files.map { |fn| Innodb::Log.new(fn) }
9
+ sizes = @logs.map { |log| log.size }
10
+ raise "Log file sizes do not match" unless sizes.uniq.size == 1
6
11
  end
7
12
 
8
- def add_log(file)
9
- if file = Innodb::Log.new(file)
10
- @files.push file
11
- else
12
- raise "Couldn't open #{file}"
13
+ # Iterate through all logs.
14
+ def each_log
15
+ unless block_given?
16
+ return enum_for(:each_log)
17
+ end
18
+
19
+ @logs.each do |log|
20
+ yield log
13
21
  end
14
22
  end
15
23
 
24
+ # Iterate through all blocks.
16
25
  def each_block
17
- @files.each do |file|
18
- file.each_block do |block_number, block|
19
- yield block_number, block
26
+ unless block_given?
27
+ return enum_for(:each_block)
28
+ end
29
+
30
+ each_log do |log|
31
+ log.each_block do |block_index, block|
32
+ yield block_index, block
20
33
  end
21
34
  end
22
35
  end
23
36
 
24
- def current_tail_position
25
- max = 0
26
- max_file = nil
27
- max_block = nil
37
+ # The number of log files in the group.
38
+ def logs
39
+ @logs.count
40
+ end
28
41
 
29
- @files.each_with_index do |file, file_number|
30
- file.each_block do |block_number, block|
31
- if block.header[:block] > max
32
- max = block.header[:block]
33
- max_file = file_number
34
- max_block = block_number
35
- end
36
- end
37
- end
42
+ # Returns the log at the given position in the log group.
43
+ def log(log_no)
44
+ @logs.at(log_no)
45
+ end
38
46
 
39
- { :file => max_file, :block => max_block }
47
+ # The size in byes of each and every log in the group.
48
+ def log_size
49
+ @logs.first.size
40
50
  end
41
51
 
42
- def successor_position(position)
43
- if position[:block] == @files[position[:file]].blocks
44
- if position[:file] == @files.size
45
- { :file => 0, :block => 0 }
46
- else
47
- { :file => position[:file] + 1, :block => 0 }
48
- end
49
- else
50
- { :file => position[:file], :block => position[:block] + 1 }
51
- end
52
+ # The size of the log group (in bytes)
53
+ def size
54
+ @logs.first.size * @logs.count
52
55
  end
53
56
 
54
- def block(file_number, block_number)
55
- @files[file_number].block(block_number)
57
+ # The log group capacity (in bytes).
58
+ def capacity
59
+ @logs.first.capacity * @logs.count
56
60
  end
57
61
 
58
- def block_if_newer(old_block, new_block)
59
- return new_block if old_block.nil?
60
- #puts "old: #{old_block.header[:block]} new: #{new_block.header[:block]}"
61
- if new_block.header[:block] >= old_block.header[:block]
62
- new_block
63
- end
62
+ # Returns the LSN coordinates of the data at the start of the log group.
63
+ def start_lsn
64
+ [@logs.first.header[:start_lsn], Innodb::Log::LOG_HEADER_SIZE]
64
65
  end
65
66
 
66
- def tail_blocks
67
- position = current_tail_position
68
- current_block = nil
69
- while true
70
- until block_if_newer(current_block, new_block = block(position[:file], position[:block]))
71
- #puts "Waiting at the tail: #{position[:file]} #{position[:block]}"
72
- sleep 0.1
73
- end
74
- yield new_block
75
- position = successor_position(position)
76
- current_block = new_block
77
- end
67
+ # Returns the LSN coordinates of the most recent (highest) checkpoint.
68
+ def max_checkpoint_lsn
69
+ checkpoint = @logs.first.checkpoint.max_by{|f,v| v[:number]}.last
70
+ checkpoint.values_at(:lsn, :lsn_offset)
71
+ end
72
+
73
+ # Returns a LogReader using the given LSN reference coordinates.
74
+ def reader(lsn_coord = start_lsn)
75
+ lsn_no, lsn_offset = lsn_coord
76
+ lsn = Innodb::LSN.new(lsn_no, lsn_offset)
77
+ Innodb::LogReader.new(lsn, self)
78
+ end
79
+
80
+ # Parse and return a record at a given LSN.
81
+ def record(lsn_no)
82
+ reader.seek(lsn_no).record
78
83
  end
79
84
  end