Skip to content
Browse files

Adds support for concurrent indexing in PostgreSQL adapter

Adds support for algorithm option in MySQL indexes
Moves USING and algorithm options upstream

The syntax is still specific to the Adapter, so the actual executed string happens
in the corresponding adapter
  • Loading branch information...
1 parent 1a838cc commit e199dc1a570d4f0d9a07628268835bce5aab2732 @danmcclain danmcclain committed Mar 25, 2013
View
12 activerecord/CHANGELOG.md
@@ -3,6 +3,18 @@
* PostgreSQL geometric type point is supported by ActiveRecord. Fixes #7324.
*Martin Schuerrer*
+
+* Add suport for concurrent indexing in PostgreSQL adapter via the
+ `algorithm: :concurrently` option
+
+ add_index(:people, :last_name, algorithm: :concurrently)
+
+ Also adds support for MySQL index algorithms (`COPY`, `INPLACE`,
+ `DEFAULT`) via the `algorithm: :copy` option
+
+ add_index(:people, :last_name, algorithm: :copy) # or :inplace/:default
+
+ *Dan McClain*
* Add an `add_index` override in Postgresql adapter and MySQL adapter
to allow custom index type support. Fixes #6101.
View
7 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -752,11 +752,13 @@ 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)
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm)
index_type = options[:unique] ? "UNIQUE" : ""
index_name = options[:name].to_s if options.key?(:name)
max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
+ algorithm = index_algorithms[options[:algorithm]]
+ using = "USING #{options[:using]}" if options[:using].present?
if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
@@ -772,6 +774,7 @@ def add_index_options(table_name, column_name, options = {})
index_type = options
max_index_length = allowed_index_name_length
+ algorithm = using = nil
end
if index_name.length > max_index_length
@@ -782,7 +785,7 @@ def add_index_options(table_name, column_name, options = {})
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options]
+ [index_name, index_type, index_columns, index_options, algorithm, using]
end
def index_name_for_remove(table_name, options = {})
View
7 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -282,6 +282,13 @@ def extensions
[]
end
+ # A list of index algorithms, to be filled by adapters that
+ # support them. MySQL and PostgreSQL has support for them right
+ # now.
+ def index_algorithms
+ {}
+ end
+
# QUOTING ==================================================
# Returns a bind substitution value given a +column+ and list of current
View
12 activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -208,6 +208,10 @@ def native_database_types
NATIVE_DATABASE_TYPES
end
+ def index_algorithms
+ { default: 'ALGORITHM = DEFAULT', copy: 'ALGORITHM = COPY', inplace: 'ALGORITHM = INPLACE' }
+ end
+
# HELPER METHODS ===========================================
# The two drivers have slightly different ways of yielding hashes of results, so
@@ -506,12 +510,8 @@ def rename_column(table_name, column_name, new_column_name) #:nodoc:
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
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options} #{index_algorithm}"
end
# Maps logical Rails types to MySQL-specific data types.
View
8 activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -410,12 +410,8 @@ def rename_column(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
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
+ execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
end
def remove_index!(table_name, index_name) #:nodoc:
View
4 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -433,6 +433,10 @@ def supports_transaction_isolation?
true
end
+ def index_algorithms
+ { concurrently: 'CONCURRENTLY' }
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
View
19 activerecord/test/cases/adapters/mysql/active_schema_test.rb
@@ -21,30 +21,33 @@ def test_add_index
ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ 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`)"
+ 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))"
+ 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))"
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ 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?)
View
19 activerecord/test/cases/adapters/mysql2/active_schema_test.rb
@@ -21,30 +21,33 @@ def test_add_index
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:define_method, :index_name_exists?) do |*|
false
end
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`) "
assert_equal expected, add_index(:people, :last_name, :length => nil)
- expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10))"
+ expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`(10)) "
assert_equal expected, add_index(:people, :last_name, :length => 10)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15))"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(15)) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => 15)
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`)"
+ expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`) "
assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15})
- expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
+ 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`)"
+ 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))"
+ 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))"
+ expected = "CREATE INDEX `index_people_on_last_name` USING btree ON `people` (`last_name`(10)) ALGORITHM = COPY"
+ assert_equal expected, add_index(:people, :last_name, :length => 10, using: :btree, algorithm: :copy)
+
+ 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?)
View
16 activerecord/test/cases/adapters/postgresql/active_schema_test.rb
@@ -29,18 +29,24 @@ def test_add_index
false
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active')
+ 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'")
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, algorithm: :concurrently)
+
%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)
+ expected = %(CREATE INDEX "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type)
+
+ expected = %(CREATE INDEX CONCURRENTLY "index_people_on_last_name" ON "people" USING #{type} ("last_name"))
+ assert_equal expected, add_index(:people, :last_name, using: type, algorithm: :concurrently)
end
- expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" USING gist ("last_name"))
+ 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')
+ 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?)

0 comments on commit e199dc1

Please sign in to comment.
Something went wrong with that request. Please try again.