innodb_ruby 0.8.8 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- key && key.map { |r| "%s=%s" % [r[:name], r[:value]] }.join(", ")
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
- this_page = Innodb::Page.parse(self, page_data(page_number))
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 data_dictionary
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, record_describer || @record_describer)
219
+ Innodb::Index.new(self, root_page_number,
220
+ record_describer || @record_describer)
223
221
  end
224
222
 
225
- # Iterate through each index by guessing that the root pages will be
226
- # present starting at page 3, and walking forward until we find a non-
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(:each_index)
226
+ return enum_for(:each_index_root_page_number)
232
227
  end
233
228
 
234
- if system_space?
235
- data_dictionary.each_index do |table_name, index_name, index|
236
- yield index
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 index(page_number)
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
@@ -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
- attr_accessor :spaces
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
- @spaces = {}
12
- @spaces[0] = Innodb::Space.new(system_space_file)
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
- @spaces[0]
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
- def each_record_from_data_dictionary_index(table, index)
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(:each_record_from_data_dictionary_index, table, index)
86
+ return enum_for(:each_table_name)
22
87
  end
23
88
 
24
- index = system_space.data_dictionary.index(table, index)
25
- index.each_record do |record|
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
- def each_table
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(:each_table)
99
+ return enum_for(:each_column_name_by_table_name, table_name)
34
100
  end
35
101
 
36
- each_record_from_data_dictionary_index(:SYS_TABLES, :PRIMARY) do |record|
37
- yield record.fields
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
- def each_index
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(:each_index)
112
+ return enum_for(:each_index_name_by_table_name, table_name)
44
113
  end
45
114
 
46
- each_record_from_data_dictionary_index(:SYS_INDEXES, :PRIMARY) do |record|
47
- yield record.fields
115
+ data_dictionary.each_index_by_table_name(table_name) do |record|
116
+ yield record["NAME"]
48
117
  end
49
- end
50
118
 
51
- def object_by_field(method, field, value)
52
- send(method).select { |o| o[field] == value }.first
119
+ nil
53
120
  end
54
121
 
55
- def object_by_two_fields(method, f1, v1, f2, v2)
56
- send(method).select { |o| o[f1] == v1 && o[f2] == v2 }.first
57
- end
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
- def table_by_id(value)
60
- object_by_field(:each_table, "ID", value)
61
- end
130
+ data_dictionary.each_field_by_index_name(table_name, index_name) do |record|
131
+ yield record["COL_NAME"]
132
+ end
62
133
 
63
- def table_by_name(value)
64
- object_by_field(:each_table, "NAME", value)
134
+ nil
65
135
  end
66
136
 
67
- def index_by_id(value)
68
- object_by_field(:each_index, "ID", value)
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
- def index_by_name(table_name, index_name)
72
- unless table = table_by_name(table_name)
73
- return nil
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
- def add_space(space)
82
- @spaces[space.space_id] = space
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
- def add_space_file(space_file)
86
- add_space(Innodb::Space.new(space_file))
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