Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for FULLTEXT and SPATIAL indexes using the :type flag for MySQL #9944

Merged
merged 1 commit into from Mar 28, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 9 additions & 1 deletion activerecord/CHANGELOG.md
Expand Up @@ -15,7 +15,7 @@

*Martin Schuerrer*

* Add suport for concurrent indexing in PostgreSQL adapter via the
* Add support for concurrent indexing in PostgreSQL adapter via the
`algorithm: :concurrently` option

add_index(:people, :last_name, algorithm: :concurrently)
Expand All @@ -27,6 +27,14 @@

*Dan McClain*

* Add support for fulltext and spatial indexes on MySQL tables with MyISAM database
engine via the `type: 'FULLTEXT'` / `type: 'SPATIAL'` option

add_index(:people, :last_name, type: 'FULLTEXT')
add_index(:people, :last_name, type: 'SPATIAL')

*Ken Mazaika*

* Add an `add_index` override in Postgresql adapter and MySQL adapter
to allow custom index type support. Fixes #6101.

Expand Down
Expand Up @@ -8,7 +8,7 @@ module ConnectionAdapters #:nodoc:
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :using) #:nodoc:
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
end

# Abstract representation of a column definition. Instances of this type
Expand Down
Expand Up @@ -498,13 +498,25 @@ def rename_column(table_name, column_name, new_column_name)
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
# ====== Creating an index with a specific method
#
# add_index(:developers, :name, :using => 'btree')
#
# generates
#
# CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
# CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
#
# Note: only supported by PostgreSQL and MySQL
#
# ====== Creating an index with a specific type
#
# add_index(:developers, :name, :type => :fulltext)
#
# generates
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put a blank line before and after this line

#
# CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
#
# Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
def add_index(table_name, column_name, options = {})
index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
Expand Down Expand Up @@ -755,9 +767,10 @@ def add_index_options(table_name, column_name, options = {})
index_name = index_name(table_name, column: column_names)

if Hash === options # legacy support, since this param was a string
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)

index_type = options[:unique] ? "UNIQUE" : ""
index_type = options[:type].to_s if options.key?(:type)
index_name = options[:name].to_s if options.key?(:name)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length

Expand Down
Expand Up @@ -151,6 +151,9 @@ def missing_default_forged_as_empty_string?(default)
:boolean => { :name => "tinyint", :limit => 1 }
}

INDEX_TYPES = [:fulltext, :spatial]
INDEX_USINGS = [:btree, :hash]

class BindSubstitution < Arel::Visitors::MySQL # :nodoc:
include Arel::Visitors::BindVisitor
end
Expand Down Expand Up @@ -435,8 +438,11 @@ def indexes(table_name, name = nil) #:nodoc:
if current_index != row[:Key_name]
next if row[:Key_name] == 'PRIMARY' # skip the primary key
current_index = row[:Key_name]
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [])
indexes.last.using = row[:Index_type].downcase.to_sym

mysql_index_type = row[:Index_type].downcase.to_sym
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
end

indexes.last.columns << row[:Column_name]
Expand Down
Expand Up @@ -160,9 +160,9 @@ def indexes(table_name, name = nil)
desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
where = inddef.scan(/WHERE (.+)$/).flatten[0]
type = inddef.scan(/USING (.+?) /).flatten[0].to_sym
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym

column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, type)
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
end.compact
end

Expand Down
2 changes: 2 additions & 0 deletions activerecord/lib/active_record/schema_dumper.rb
Expand Up @@ -187,6 +187,8 @@ def indexes(table, stream)

statement_parts << ('using: ' + index.using.inspect) if index.using

statement_parts << ('type: ' + index.type.inspect) if index.type

' ' + statement_parts.join(', ')
end

Expand Down
11 changes: 8 additions & 3 deletions activerecord/test/cases/adapters/mysql/active_schema_test.rb
Expand Up @@ -36,9 +36,14 @@ def test_add_index
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})

%w(btree hash).each do |type|
expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :using => type)
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :type => type)
end

%w(btree hash).each do |using|
expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :using => using)
end

expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
Expand Down
29 changes: 16 additions & 13 deletions activerecord/test/cases/adapters/mysql/schema_test.rb
Expand Up @@ -37,22 +37,25 @@ def test_table_exists_wrong_schema
end

def test_dump_indexes
index_a_name = 'index_post_title'
index_b_name = 'index_post_body'
index_a_name = 'index_key_tests_on_snack'
index_b_name = 'index_key_tests_on_pizza'
index_c_name = 'index_key_tests_on_awesome'

table = Post.table_name

@connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);"
@connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));"
table = 'key_tests'

indexes = @connection.indexes(table).sort_by {|i| i.name}
assert_equal 2,indexes.size

assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using
assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using

@connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;"
@connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;"
assert_equal 3,indexes.size

index_a = indexes.select{|i| i.name == index_a_name}[0]
index_b = indexes.select{|i| i.name == index_b_name}[0]
index_c = indexes.select{|i| i.name == index_c_name}[0]
assert_equal :btree, index_a.using
assert_nil index_a.type
assert_equal :btree, index_b.using
assert_nil index_b.type

assert_nil index_c.using
assert_equal :fulltext, index_c.type
end
end
end
Expand Down
11 changes: 8 additions & 3 deletions activerecord/test/cases/adapters/mysql2/active_schema_test.rb
Expand Up @@ -36,9 +36,14 @@ def test_add_index
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})

%w(btree hash).each do |type|
expected = "CREATE INDEX `index_people_on_last_name` USING #{type} ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :using => type)
%w(SPATIAL FULLTEXT UNIQUE).each do |type|
expected = "CREATE #{type} INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :type => type)
end

%w(btree hash).each do |using|
expected = "CREATE INDEX `index_people_on_last_name` USING #{using} ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :using => using)
end

expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) "
Expand Down
25 changes: 14 additions & 11 deletions activerecord/test/cases/adapters/mysql2/schema_test.rb
Expand Up @@ -45,22 +45,25 @@ def test_tables_quoting
end

def test_dump_indexes
index_a_name = 'index_post_title'
index_b_name = 'index_post_body'
index_a_name = 'index_key_tests_on_snack'
index_b_name = 'index_key_tests_on_pizza'
index_c_name = 'index_key_tests_on_awesome'

table = Post.table_name

@connection.execute "CREATE INDEX `#{index_a_name}` ON `#{table}` (`title`);"
@connection.execute "CREATE INDEX `#{index_b_name}` USING btree ON `#{table}` (`body`(10));"
table = 'key_tests'

indexes = @connection.indexes(table).sort_by {|i| i.name}
assert_equal 2,indexes.size
assert_equal 3,indexes.size

assert_equal :btree, indexes.select{|i| i.name == index_a_name}[0].using
assert_equal :btree, indexes.select{|i| i.name == index_b_name}[0].using
index_a = indexes.select{|i| i.name == index_a_name}[0]
index_b = indexes.select{|i| i.name == index_b_name}[0]
index_c = indexes.select{|i| i.name == index_c_name}[0]
assert_equal :btree, index_a.using
assert_nil index_a.type
assert_equal :btree, index_b.using
assert_nil index_b.type

@connection.execute "DROP INDEX `#{index_a_name}` ON `#{table}`;"
@connection.execute "DROP INDEX `#{index_b_name}` ON `#{table}`;"
assert_nil index_c.using
assert_equal :fulltext, index_c.type
end
end
end
Expand Down
6 changes: 6 additions & 0 deletions activerecord/test/cases/schema_dumper_test.rb
Expand Up @@ -225,6 +225,12 @@ def test_schema_dump_includes_length_for_mysql_blob_and_text_fields
assert_match %r{t.text\s+"medium_text",\s+limit: 16777215$}, output
assert_match %r{t.text\s+"long_text",\s+limit: 2147483647$}, output
end

def test_schema_dumps_index_type
output = standard_dump
assert_match %r{add_index "key_tests", \["awesome"\], name: "index_key_tests_on_awesome", type: :fulltext}, output
assert_match %r{add_index "key_tests", \["pizza"\], name: "index_key_tests_on_pizza", using: :btree}, output
end
end

def test_schema_dump_includes_decimal_options
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/schema/mysql2_specific_schema.rb
Expand Up @@ -14,6 +14,16 @@

add_index :binary_fields, :var_binary

create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
t.string :awesome
t.string :pizza
t.string :snacks
end

add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'

ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
Expand Down
10 changes: 10 additions & 0 deletions activerecord/test/schema/mysql_specific_schema.rb
Expand Up @@ -14,6 +14,16 @@

add_index :binary_fields, :var_binary

create_table :key_tests, force: true, :options => 'ENGINE=MyISAM' do |t|
t.string :awesome
t.string :pizza
t.string :snacks
end

add_index :key_tests, :awesome, :type => :fulltext, :name => 'index_key_tests_on_awesome'
add_index :key_tests, :pizza, :using => :btree, :name => 'index_key_tests_on_pizza'
add_index :key_tests, :snacks, :name => 'index_key_tests_on_snack'

ActiveRecord::Base.connection.execute <<-SQL
DROP PROCEDURE IF EXISTS ten;
SQL
Expand Down