mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #9891 from doabit/additional_index_support
Custom index type support with :using. Closes #9451
This commit is contained in:
commit
31388fa792
11 changed files with 125 additions and 9 deletions
|
@ -1,5 +1,12 @@
|
|||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Add an `add_index` override in Postgresql adapter and MySQL adapter
|
||||
to allow custom index type support. Fixes #6101.
|
||||
|
||||
add_index(:wikis, :body, :using => 'gin')
|
||||
|
||||
*Stefan Huber* and *Doabit*
|
||||
|
||||
* After extraction of mass-assignment attributes (which protects [id, type]
|
||||
by default) we can pass id to `update_attributes` and it will update
|
||||
another record because id will be used in where statement. We never have
|
||||
|
|
|
@ -8,7 +8,7 @@ module ActiveRecord
|
|||
# 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) #:nodoc:
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :using) #:nodoc:
|
||||
end
|
||||
|
||||
# Abstract representation of a column definition. Instances of this type
|
||||
|
|
|
@ -497,7 +497,14 @@ module ActiveRecord
|
|||
#
|
||||
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
|
||||
#
|
||||
# Note: only supported by PostgreSQL.
|
||||
# ====== 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
|
||||
#
|
||||
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}"
|
||||
|
@ -745,7 +752,7 @@ module ActiveRecord
|
|||
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)
|
||||
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using)
|
||||
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name].to_s if options.key?(:name)
|
||||
|
|
|
@ -432,6 +432,7 @@ module ActiveRecord
|
|||
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
|
||||
end
|
||||
|
||||
indexes.last.columns << row[:Column_name]
|
||||
|
@ -504,6 +505,15 @@ module ActiveRecord
|
|||
rename_column_indexes(table_name, column_name, new_column_name)
|
||||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
if options.is_a?(Hash) && options[:using]
|
||||
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)} USING #{options[:using]} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Maps logical Rails types to MySQL-specific data types.
|
||||
def type_to_sql(type, limit = nil, precision = nil, scale = nil)
|
||||
case type.to_s
|
||||
|
|
|
@ -160,8 +160,9 @@ module ActiveRecord
|
|||
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
|
||||
|
||||
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
|
||||
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, type)
|
||||
end.compact
|
||||
end
|
||||
|
||||
|
@ -408,6 +409,15 @@ module ActiveRecord
|
|||
rename_column_indexes(table_name, column_name, new_column_name)
|
||||
end
|
||||
|
||||
def add_index(table_name, column_name, options = {}) #:nodoc:
|
||||
if options.is_a?(Hash) && options[:using]
|
||||
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)} USING #{options[:using]} (#{index_columns})#{index_options}"
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def remove_index!(table_name, index_name) #:nodoc:
|
||||
execute "DROP INDEX #{quote_table_name(index_name)}"
|
||||
end
|
||||
|
|
|
@ -35,6 +35,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
|
|||
|
||||
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)
|
||||
end
|
||||
|
||||
expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10))"
|
||||
assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
|
||||
|
||||
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15))"
|
||||
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
|
||||
|
||||
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
|
||||
end
|
||||
|
||||
|
|
|
@ -35,6 +35,25 @@ module ActiveRecord
|
|||
def test_table_exists_wrong_schema
|
||||
assert(!@connection.table_exists?("#{@db_name}.zomg"), "table should not exist")
|
||||
end
|
||||
|
||||
def test_dump_indexes
|
||||
index_a_name = 'index_post_title'
|
||||
index_b_name = 'index_post_body'
|
||||
|
||||
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));"
|
||||
|
||||
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}`;"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,6 +35,18 @@ class ActiveSchemaTest < ActiveRecord::TestCase
|
|||
|
||||
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)
|
||||
end
|
||||
|
||||
expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10))"
|
||||
assert_equal expected, add_index(:people, :last_name, :length => 10, :using => :btree)
|
||||
|
||||
expected = "CREATE INDEX `index_people_on_last_name_and_first_name` USING btree ON `people` (`last_name`(15), `first_name`(15))"
|
||||
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15, :using => :btree)
|
||||
|
||||
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:remove_method, :index_name_exists?)
|
||||
end
|
||||
|
||||
|
|
|
@ -44,6 +44,24 @@ module ActiveRecord
|
|||
assert_match(/database 'foo-bar'/, e.inspect)
|
||||
end
|
||||
|
||||
def test_dump_indexes
|
||||
index_a_name = 'index_post_title'
|
||||
index_b_name = 'index_post_body'
|
||||
|
||||
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));"
|
||||
|
||||
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}`;"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,6 +32,17 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
|
|||
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
|
||||
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'")
|
||||
|
||||
%w(gin gist hash btree).each do |type|
|
||||
expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
|
||||
assert_equal expected, add_index(:people, :last_name, :using => type)
|
||||
end
|
||||
|
||||
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
|
||||
assert_equal expected, add_index(:people, :last_name, :unique => true, :using => :gist)
|
||||
|
||||
expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name") WHERE state = 'active')
|
||||
assert_equal expected, add_index(:people, :last_name, :unique => true, :where => "state = 'active'", :using => :gist)
|
||||
|
||||
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
|
||||
end
|
||||
|
||||
|
|
|
@ -11,16 +11,19 @@ class SchemaTest < ActiveRecord::TestCase
|
|||
INDEX_B_NAME = 'b_index_things_on_different_columns_in_each_schema'
|
||||
INDEX_C_NAME = 'c_index_full_text_search'
|
||||
INDEX_D_NAME = 'd_index_things_on_description_desc'
|
||||
INDEX_E_NAME = 'e_index_things_on_name_vector'
|
||||
INDEX_A_COLUMN = 'name'
|
||||
INDEX_B_COLUMN_S1 = 'email'
|
||||
INDEX_B_COLUMN_S2 = 'moment'
|
||||
INDEX_C_COLUMN = %q{(to_tsvector('english', coalesce(things.name, '')))}
|
||||
INDEX_D_COLUMN = 'description'
|
||||
INDEX_E_COLUMN = 'name_vector'
|
||||
COLUMNS = [
|
||||
'id integer',
|
||||
'name character varying(50)',
|
||||
'email character varying(50)',
|
||||
'description character varying(100)',
|
||||
'name_vector tsvector',
|
||||
'moment timestamp without time zone default now()'
|
||||
]
|
||||
PK_TABLE_NAME = 'table_with_pk'
|
||||
|
@ -61,6 +64,8 @@ class SchemaTest < ActiveRecord::TestCase
|
|||
@connection.execute "CREATE INDEX #{INDEX_C_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_C_COLUMN});"
|
||||
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
|
||||
@connection.execute "CREATE INDEX #{INDEX_D_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING btree (#{INDEX_D_COLUMN} DESC);"
|
||||
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
|
||||
@connection.execute "CREATE INDEX #{INDEX_E_NAME} ON #{SCHEMA2_NAME}.#{TABLE_NAME} USING gin (#{INDEX_E_COLUMN});"
|
||||
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{PK_TABLE_NAME} (id serial primary key)"
|
||||
@connection.execute "CREATE SEQUENCE #{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}"
|
||||
@connection.execute "CREATE TABLE #{SCHEMA_NAME}.#{UNMATCHED_PK_TABLE_NAME} (id integer NOT NULL DEFAULT nextval('#{SCHEMA_NAME}.#{UNMATCHED_SEQUENCE_NAME}'::regclass), CONSTRAINT unmatched_pkey PRIMARY KEY (id))"
|
||||
|
@ -236,15 +241,15 @@ class SchemaTest < ActiveRecord::TestCase
|
|||
end
|
||||
|
||||
def test_dump_indexes_for_schema_one
|
||||
do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
|
||||
do_dump_index_tests_for_schema(SCHEMA_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
|
||||
end
|
||||
|
||||
def test_dump_indexes_for_schema_two
|
||||
do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN)
|
||||
do_dump_index_tests_for_schema(SCHEMA2_NAME, INDEX_A_COLUMN, INDEX_B_COLUMN_S2, INDEX_D_COLUMN, INDEX_E_COLUMN)
|
||||
end
|
||||
|
||||
def test_dump_indexes_for_schema_multiple_schemas_in_search_path
|
||||
do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN)
|
||||
do_dump_index_tests_for_schema("public, #{SCHEMA_NAME}", INDEX_A_COLUMN, INDEX_B_COLUMN_S1, INDEX_D_COLUMN, INDEX_E_COLUMN)
|
||||
end
|
||||
|
||||
def test_with_uppercase_index_name
|
||||
|
@ -344,15 +349,20 @@ class SchemaTest < ActiveRecord::TestCase
|
|||
@connection.schema_search_path = "'$user', public"
|
||||
end
|
||||
|
||||
def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name)
|
||||
def do_dump_index_tests_for_schema(this_schema_name, first_index_column_name, second_index_column_name, third_index_column_name, fourth_index_column_name)
|
||||
with_schema_search_path(this_schema_name) do
|
||||
indexes = @connection.indexes(TABLE_NAME).sort_by {|i| i.name}
|
||||
assert_equal 3,indexes.size
|
||||
assert_equal 4,indexes.size
|
||||
|
||||
do_dump_index_assertions_for_one_index(indexes[0], INDEX_A_NAME, first_index_column_name)
|
||||
do_dump_index_assertions_for_one_index(indexes[1], INDEX_B_NAME, second_index_column_name)
|
||||
do_dump_index_assertions_for_one_index(indexes[2], INDEX_D_NAME, third_index_column_name)
|
||||
do_dump_index_assertions_for_one_index(indexes[3], INDEX_E_NAME, fourth_index_column_name)
|
||||
|
||||
indexes.select{|i| i.name != INDEX_E_NAME}.each do |index|
|
||||
assert_equal :btree, index.using
|
||||
end
|
||||
assert_equal :gin, indexes.select{|i| i.name == INDEX_E_NAME}[0].using
|
||||
assert_equal :desc, indexes.select{|i| i.name == INDEX_D_NAME}[0].orders[INDEX_D_COLUMN]
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue