innodb_ruby 0.8.8 → 0.9.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.
- data/README.md +6 -1
- data/bin/innodb_log +8 -6
- data/bin/innodb_space +344 -212
- data/lib/innodb.rb +12 -1
- data/lib/innodb/cursor.rb +2 -2
- data/lib/innodb/data_dictionary.rb +591 -0
- data/lib/innodb/index.rb +149 -178
- data/lib/innodb/log.rb +96 -33
- data/lib/innodb/log_block.rb +34 -68
- data/lib/innodb/page.rb +1 -7
- data/lib/innodb/page/index.rb +303 -39
- data/lib/innodb/page/index_compressed.rb +4 -0
- data/lib/innodb/page/sys_data_dictionary_header.rb +1 -43
- data/lib/innodb/record.rb +34 -3
- data/lib/innodb/record_describer.rb +2 -0
- data/lib/innodb/space.rb +37 -22
- data/lib/innodb/stats.rb +46 -0
- data/lib/innodb/system.rb +122 -42
- data/lib/innodb/version.rb +1 -1
- metadata +3 -2
@@ -1,5 +1,9 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
+
# This is horribly incomplete and broken. InnoDB compression does not
|
4
|
+
# currently work in innodb_ruby. Patches are welcome!
|
5
|
+
# (Hint hint, nudge nudge, Facebook developers!)
|
6
|
+
|
3
7
|
class Innodb::Page::Index::Compressed < Innodb::Page::Index
|
4
8
|
# The number of directory slots in use.
|
5
9
|
def directory_slots
|
@@ -1,18 +1,6 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
-
require "innodb/data_dictionary"
|
4
|
-
|
5
3
|
class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
6
|
-
RECORD_DESCRIBERS = {
|
7
|
-
:SYS_TABLES => {
|
8
|
-
:PRIMARY => Innodb::DataDictionary::SYS_TABLES_PRIMARY,
|
9
|
-
:ID => Innodb::DataDictionary::SYS_TABLES_ID
|
10
|
-
},
|
11
|
-
:SYS_COLUMNS => { :PRIMARY => Innodb::DataDictionary::SYS_COLUMNS_PRIMARY },
|
12
|
-
:SYS_INDEXES => { :PRIMARY => Innodb::DataDictionary::SYS_INDEXES_PRIMARY },
|
13
|
-
:SYS_FIELDS => { :PRIMARY => Innodb::DataDictionary::SYS_FIELDS_PRIMARY },
|
14
|
-
}
|
15
|
-
|
16
4
|
# The position of the data dictionary header within the page.
|
17
5
|
def pos_data_dictionary_header
|
18
6
|
pos_fil_header + size_fil_header
|
@@ -35,7 +23,7 @@ class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
|
35
23
|
:indexes => c.name("indexes") {{
|
36
24
|
:SYS_TABLES => c.name("SYS_TABLES") {{
|
37
25
|
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
38
|
-
:ID => c.name("ID") { c.get_uint32 },
|
26
|
+
:ID => c.name("ID") { c.get_uint32 },
|
39
27
|
}},
|
40
28
|
:SYS_COLUMNS => c.name("SYS_COLUMNS") {{
|
41
29
|
:PRIMARY => c.name("PRIMARY") { c.get_uint32 },
|
@@ -53,36 +41,6 @@ class Innodb::Page::SysDataDictionaryHeader < Innodb::Page
|
|
53
41
|
end
|
54
42
|
end
|
55
43
|
|
56
|
-
def index(table_name, index_name)
|
57
|
-
unless table_entry = data_dictionary_header[:indexes][table_name]
|
58
|
-
raise "Unknown data dictionary table #{table_name}"
|
59
|
-
end
|
60
|
-
|
61
|
-
unless index_root_page = table_entry[index_name]
|
62
|
-
raise "Unknown data dictionary index #{table_name}.#{index_name}"
|
63
|
-
end
|
64
|
-
|
65
|
-
# If we have a record describer for this index, load it.
|
66
|
-
record_describer = RECORD_DESCRIBERS[table_name] &&
|
67
|
-
RECORD_DESCRIBERS[table_name][index_name]
|
68
|
-
|
69
|
-
@space.index(index_root_page, record_describer.new)
|
70
|
-
end
|
71
|
-
|
72
|
-
# Iterate through all indexes in the data dictionary, yielding the table
|
73
|
-
# name, index name, and the index itself as an Innodb::Index.
|
74
|
-
def each_index
|
75
|
-
unless block_given?
|
76
|
-
return enum_for(:each_index)
|
77
|
-
end
|
78
|
-
|
79
|
-
data_dictionary_header[:indexes].each do |table_name, indexes|
|
80
|
-
indexes.each do |index_name, root_page_number|
|
81
|
-
yield table_name, index_name, index(table_name, index_name)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
44
|
def dump
|
87
45
|
super
|
88
46
|
|
data/lib/innodb/record.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
3
|
class Innodb::Record
|
4
|
+
attr_reader :page
|
4
5
|
attr_accessor :record
|
5
6
|
|
6
|
-
def initialize(record)
|
7
|
+
def initialize(page, record)
|
8
|
+
@page = page
|
7
9
|
@record = record
|
8
10
|
end
|
9
11
|
|
@@ -24,7 +26,7 @@ class Innodb::Record
|
|
24
26
|
end
|
25
27
|
|
26
28
|
def key_string
|
27
|
-
key && key.map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", ")
|
29
|
+
key && key.map { |r| "%s=%s" % [r[:name], r[:value].inspect] }.join(", ")
|
28
30
|
end
|
29
31
|
|
30
32
|
def row
|
@@ -32,13 +34,21 @@ class Innodb::Record
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def row_string
|
35
|
-
|
37
|
+
row && row.map { |r| "%s=%s" % [r[:name], r[:value].inspect] }.join(", ")
|
36
38
|
end
|
37
39
|
|
38
40
|
def child_page_number
|
39
41
|
record[:child_page_number]
|
40
42
|
end
|
41
43
|
|
44
|
+
def string
|
45
|
+
if child_page_number
|
46
|
+
"(%s) → #%s" % [key_string, child_page_number]
|
47
|
+
else
|
48
|
+
"(%s) → (%s)" % [key_string, row_string]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
42
52
|
def uncached_fields
|
43
53
|
fields_hash = {}
|
44
54
|
[:key, :row].each do |group|
|
@@ -54,4 +64,25 @@ class Innodb::Record
|
|
54
64
|
def fields
|
55
65
|
@fields ||= uncached_fields
|
56
66
|
end
|
67
|
+
|
68
|
+
# Compare two arrays of fields to determine if they are equal. This follows
|
69
|
+
# the same comparison rules as strcmp and others:
|
70
|
+
# 0 = a is equal to b
|
71
|
+
# -1 = a is less than b
|
72
|
+
# +1 = a is greater than b
|
73
|
+
def compare_key(other_key)
|
74
|
+
Innodb::Stats.increment :compare_key
|
75
|
+
|
76
|
+
return 0 if other_key.nil? && key.nil?
|
77
|
+
return -1 if other_key.nil? || (!key.nil? && other_key.size < key.size)
|
78
|
+
return +1 if key.nil? || (!other_key.nil? && other_key.size > key.size)
|
79
|
+
|
80
|
+
key.each_index do |i|
|
81
|
+
Innodb::Stats.increment :compare_key_field_comparison
|
82
|
+
return -1 if other_key[i] < key[i][:value]
|
83
|
+
return +1 if other_key[i] > key[i][:value]
|
84
|
+
end
|
85
|
+
|
86
|
+
return 0
|
87
|
+
end
|
57
88
|
end
|
@@ -88,6 +88,8 @@ class Innodb::RecordDescriber
|
|
88
88
|
|
89
89
|
def initialize
|
90
90
|
@description = self.class.static_description.dup
|
91
|
+
@description[:key] = @description[:key].dup
|
92
|
+
@description[:row] = @description[:row].dup
|
91
93
|
end
|
92
94
|
|
93
95
|
# Set the type of this record (:clustered or :secondary).
|
data/lib/innodb/space.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
# An InnoDB space file, which can be either a multi-table ibdataN file
|
4
4
|
# or a single-table "innodb_file_per_table" .ibd file.
|
5
|
-
|
6
5
|
class Innodb::Space
|
7
6
|
# InnoDB's default page size is 16KiB.
|
8
7
|
DEFAULT_PAGE_SIZE = 16384
|
@@ -35,9 +34,13 @@ class Innodb::Space
|
|
35
34
|
|
36
35
|
@pages = (@size / @page_size)
|
37
36
|
@compressed = fsp_flags[:compressed]
|
37
|
+
@innodb_system = nil
|
38
38
|
@record_describer = nil
|
39
39
|
end
|
40
40
|
|
41
|
+
# The Innodb::System to which this space belongs, if any.
|
42
|
+
attr_accessor :innodb_system
|
43
|
+
|
41
44
|
# An object which can be used to describe records found in pages within
|
42
45
|
# this space.
|
43
46
|
attr_accessor :record_describer
|
@@ -142,13 +145,7 @@ class Innodb::Space
|
|
142
145
|
|
143
146
|
# Get an Innodb::Page object for a specific page by page number.
|
144
147
|
def page(page_number)
|
145
|
-
|
146
|
-
|
147
|
-
if this_page.type == :INDEX
|
148
|
-
this_page.record_describer = @record_describer
|
149
|
-
end
|
150
|
-
|
151
|
-
this_page
|
148
|
+
Innodb::Page.parse(self, page_data(page_number))
|
152
149
|
end
|
153
150
|
|
154
151
|
# Determine whether this space looks like a system space. If the initial
|
@@ -206,7 +203,7 @@ class Innodb::Space
|
|
206
203
|
end
|
207
204
|
|
208
205
|
# Get the Innodb::Page::SysDataDictionaryHeader page for a system space.
|
209
|
-
def
|
206
|
+
def data_dictionary_page
|
210
207
|
page(page_sys_data_dictionary) if system_space?
|
211
208
|
end
|
212
209
|
|
@@ -219,32 +216,47 @@ class Innodb::Space
|
|
219
216
|
|
220
217
|
# Get an Innodb::Index object for a specific index by root page number.
|
221
218
|
def index(root_page_number, record_describer=nil)
|
222
|
-
Innodb::Index.new(self, root_page_number,
|
219
|
+
Innodb::Index.new(self, root_page_number,
|
220
|
+
record_describer || @record_describer)
|
223
221
|
end
|
224
222
|
|
225
|
-
# Iterate through
|
226
|
-
|
227
|
-
# root page. This should work fine for IBD files, but not for ibdata
|
228
|
-
# files.
|
229
|
-
def each_index
|
223
|
+
# Iterate through all root page numbers for indexes in the space.
|
224
|
+
def each_index_root_page_number
|
230
225
|
unless block_given?
|
231
|
-
return enum_for(:
|
226
|
+
return enum_for(:each_index_root_page_number)
|
232
227
|
end
|
233
228
|
|
234
|
-
if
|
235
|
-
|
236
|
-
|
229
|
+
if innodb_system
|
230
|
+
# Retrieve the index root page numbers from the data dictionary.
|
231
|
+
innodb_system.data_dictionary.each_index_by_space_id(space_id) do |record|
|
232
|
+
yield record["PAGE_NO"]
|
237
233
|
end
|
238
234
|
else
|
235
|
+
# Guess that the index root pages will be present starting at page 3,
|
236
|
+
# and walk forward until we find a non-root page. This should work fine
|
237
|
+
# for IBD files, if they haven't added indexes online.
|
239
238
|
(3...@pages).each do |page_number|
|
240
239
|
page = page(page_number)
|
241
240
|
if page.type == :INDEX && page.root?
|
242
|
-
yield
|
243
|
-
else
|
244
|
-
break
|
241
|
+
yield page_number
|
245
242
|
end
|
246
243
|
end
|
247
244
|
end
|
245
|
+
|
246
|
+
nil
|
247
|
+
end
|
248
|
+
|
249
|
+
# Iterate through all indexes in the space.
|
250
|
+
def each_index
|
251
|
+
unless block_given?
|
252
|
+
return enum_for(:each_index)
|
253
|
+
end
|
254
|
+
|
255
|
+
each_index_root_page_number do |page_number|
|
256
|
+
yield index(page_number)
|
257
|
+
end
|
258
|
+
|
259
|
+
nil
|
248
260
|
end
|
249
261
|
|
250
262
|
# An array of Innodb::Inode list names.
|
@@ -336,6 +348,8 @@ class Innodb::Space
|
|
336
348
|
end
|
337
349
|
end
|
338
350
|
|
351
|
+
# Iterate through all pages, yielding the page number, page object,
|
352
|
+
# and page status.
|
339
353
|
def each_page_status(start_page=0)
|
340
354
|
unless block_given?
|
341
355
|
return enum_for(:each_page_with_status, start_page)
|
@@ -353,6 +367,7 @@ class Innodb::Space
|
|
353
367
|
end
|
354
368
|
end
|
355
369
|
|
370
|
+
# A helper to produce a printable page type.
|
356
371
|
def type_for_page(page, page_status)
|
357
372
|
page_status[:free] ? "FREE (#{page.type})" : page.type
|
358
373
|
end
|
data/lib/innodb/stats.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
# Collect stats globally within innodb_ruby for comparison purposes and for
|
4
|
+
# correctness checking.
|
5
|
+
class Innodb::Stats
|
6
|
+
@@data = Hash.new(0)
|
7
|
+
|
8
|
+
# Return the data hash directly.
|
9
|
+
def self.data
|
10
|
+
@@data
|
11
|
+
end
|
12
|
+
|
13
|
+
# Increment a statistic by name (typically a symbol), optionally by a value
|
14
|
+
# provided.
|
15
|
+
def self.increment(name, value=1)
|
16
|
+
@@data[name] += value
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get a statistic by name.
|
20
|
+
def self.get(name)
|
21
|
+
@@data[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
# Reset all statistics.
|
25
|
+
def self.reset
|
26
|
+
@@data.clear
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Print a simple report of collected statistics, optionally to the IO object
|
31
|
+
# provided, or by default to STDOUT.
|
32
|
+
def self.print_report(io=STDOUT)
|
33
|
+
io.puts "%-50s%10s" % [
|
34
|
+
"Statistic",
|
35
|
+
"Count",
|
36
|
+
]
|
37
|
+
@@data.sort.each do |name, count|
|
38
|
+
io.puts "%-50s%10i" % [
|
39
|
+
name,
|
40
|
+
count
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
data/lib/innodb/system.rb
CHANGED
@@ -1,88 +1,168 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
+
# A class representing an entire InnoDB system, having a system tablespace
|
4
|
+
# and any number of attached single-table tablespaces.
|
3
5
|
class Innodb::System
|
6
|
+
# A hash of configuration options by configuration key.
|
4
7
|
attr_reader :config
|
5
|
-
|
8
|
+
|
9
|
+
# A hash of spaces by space ID.
|
10
|
+
attr_reader :spaces
|
11
|
+
|
12
|
+
# The Innodb::DataDictionary for this system.
|
13
|
+
attr_reader :data_dictionary
|
14
|
+
|
15
|
+
# The space ID of the system space, always 0.
|
16
|
+
SYSTEM_SPACE_ID = 0
|
6
17
|
|
7
18
|
def initialize(system_space_file)
|
19
|
+
@spaces = {}
|
8
20
|
@config = {
|
9
|
-
:datadir =>
|
21
|
+
:datadir => File.dirname(system_space_file),
|
10
22
|
}
|
11
|
-
|
12
|
-
|
23
|
+
|
24
|
+
add_space_file(system_space_file)
|
25
|
+
|
26
|
+
@data_dictionary = Innodb::DataDictionary.new(system_space)
|
13
27
|
end
|
14
28
|
|
29
|
+
# A helper to get the system space.
|
15
30
|
def system_space
|
16
|
-
|
31
|
+
spaces[SYSTEM_SPACE_ID]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add an already-constructed Innodb::Space object.
|
35
|
+
def add_space(space)
|
36
|
+
unless space.is_a?(Innodb::Space)
|
37
|
+
raise "Object was not an Innodb::Space"
|
38
|
+
end
|
39
|
+
|
40
|
+
spaces[space.space_id.to_i] = space
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add a space by filename.
|
44
|
+
def add_space_file(space_file)
|
45
|
+
space = Innodb::Space.new(space_file)
|
46
|
+
space.innodb_system = self
|
47
|
+
add_space(space)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a space by table name, constructing an appropriate filename
|
51
|
+
# from the provided table name.
|
52
|
+
def add_table(table_name)
|
53
|
+
add_space_file("%s/%s.ibd" % [config[:datadir], table_name])
|
17
54
|
end
|
18
55
|
|
19
|
-
|
56
|
+
# Return an Innodb::Space object for a given space ID, looking up
|
57
|
+
# and adding the single-table space if necessary.
|
58
|
+
def space(space_id)
|
59
|
+
return spaces[space_id] if spaces[space_id]
|
60
|
+
|
61
|
+
unless table_record = data_dictionary.table_by_space_id(space_id)
|
62
|
+
raise "Table with space ID #{space_id} not found"
|
63
|
+
end
|
64
|
+
|
65
|
+
add_table(table_record["NAME"])
|
66
|
+
|
67
|
+
spaces[space_id]
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return an Innodb::Space object by table name.
|
71
|
+
def space_by_table_name(table_name)
|
72
|
+
unless table_record = data_dictionary.table_by_name(table_name)
|
73
|
+
raise "Table #{table_name} not found"
|
74
|
+
end
|
75
|
+
|
76
|
+
if table_record["SPACE"] == 0
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
space(table_record["SPACE"])
|
81
|
+
end
|
82
|
+
|
83
|
+
# Iterate through all table names.
|
84
|
+
def each_table_name
|
20
85
|
unless block_given?
|
21
|
-
return enum_for(:
|
86
|
+
return enum_for(:each_table_name)
|
22
87
|
end
|
23
88
|
|
24
|
-
|
25
|
-
|
26
|
-
yield record
|
89
|
+
data_dictionary.each_table do |record|
|
90
|
+
yield record["NAME"]
|
27
91
|
end
|
92
|
+
|
28
93
|
nil
|
29
94
|
end
|
30
95
|
|
31
|
-
|
96
|
+
# Iterate through all column names by table name.
|
97
|
+
def each_column_name_by_table_name(table_name)
|
32
98
|
unless block_given?
|
33
|
-
return enum_for(:
|
99
|
+
return enum_for(:each_column_name_by_table_name, table_name)
|
34
100
|
end
|
35
101
|
|
36
|
-
|
37
|
-
yield record
|
102
|
+
data_dictionary.each_column_by_table_name(table_name) do |record|
|
103
|
+
yield record["NAME"]
|
38
104
|
end
|
105
|
+
|
106
|
+
nil
|
39
107
|
end
|
40
108
|
|
41
|
-
|
109
|
+
# Iterate through all index names by table name.
|
110
|
+
def each_index_name_by_table_name(table_name)
|
42
111
|
unless block_given?
|
43
|
-
return enum_for(:
|
112
|
+
return enum_for(:each_index_name_by_table_name, table_name)
|
44
113
|
end
|
45
114
|
|
46
|
-
|
47
|
-
yield record
|
115
|
+
data_dictionary.each_index_by_table_name(table_name) do |record|
|
116
|
+
yield record["NAME"]
|
48
117
|
end
|
49
|
-
end
|
50
118
|
|
51
|
-
|
52
|
-
send(method).select { |o| o[field] == value }.first
|
119
|
+
nil
|
53
120
|
end
|
54
121
|
|
55
|
-
|
56
|
-
|
57
|
-
|
122
|
+
# Iterate through all field names in a given index by table name
|
123
|
+
# and index name.
|
124
|
+
def each_index_field_name_by_index_name(table_name, index_name)
|
125
|
+
unless block_given?
|
126
|
+
return enum_for(:each_index_field_name_by_index_name,
|
127
|
+
table_name, index_name)
|
128
|
+
end
|
58
129
|
|
59
|
-
|
60
|
-
|
61
|
-
|
130
|
+
data_dictionary.each_field_by_index_name(table_name, index_name) do |record|
|
131
|
+
yield record["COL_NAME"]
|
132
|
+
end
|
62
133
|
|
63
|
-
|
64
|
-
object_by_field(:each_table, "NAME", value)
|
134
|
+
nil
|
65
135
|
end
|
66
136
|
|
67
|
-
|
68
|
-
|
137
|
+
# Return the table name given a table ID.
|
138
|
+
def table_name_by_id(table_id)
|
139
|
+
if table_record = data_dictionary.table_by_id(table_id)
|
140
|
+
table_record["NAME"]
|
141
|
+
end
|
69
142
|
end
|
70
143
|
|
71
|
-
|
72
|
-
|
73
|
-
|
144
|
+
# Return the index name given an index ID.
|
145
|
+
def index_name_by_id(index_id)
|
146
|
+
if index_record = data_dictionary.index_by_id(index_id)
|
147
|
+
index_record["NAME"]
|
74
148
|
end
|
75
|
-
|
76
|
-
object_by_two_fields(:each_index,
|
77
|
-
"TABLE_ID", table["ID"],
|
78
|
-
"NAME", index_name)
|
79
149
|
end
|
80
150
|
|
81
|
-
|
82
|
-
|
151
|
+
# Return an array of the table name and index name given an index ID.
|
152
|
+
def table_and_index_name_by_id(index_id)
|
153
|
+
if index_record = data_dictionary.index_by_id(index_id)
|
154
|
+
[table_name_by_id(index_record["TABLE_ID"]), index_record["NAME"]]
|
155
|
+
end
|
83
156
|
end
|
84
157
|
|
85
|
-
|
86
|
-
|
158
|
+
# Return an Innodb::Index object given a table name and index name.
|
159
|
+
def index_by_name(table_name, index_name)
|
160
|
+
index_record = data_dictionary.index_by_name(table_name, index_name)
|
161
|
+
|
162
|
+
index_space = space(index_record["SPACE"])
|
163
|
+
describer = data_dictionary.record_describer_by_index_name(table_name, index_name)
|
164
|
+
index = index_space.index(index_record["PAGE_NO"], describer)
|
165
|
+
|
166
|
+
index
|
87
167
|
end
|
88
168
|
end
|