Skip to content
Permalink
Browse files
Merge pull request #9923 from danmcclain/psql-concurrent-indexes
Adds support for concurrent indexing in PostgreSQL adapter
  • Loading branch information
rafaelfranca committed Mar 26, 2013
2 parents 078bfbf + 203e0e0 commit 2d33796
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 35 deletions.
@@ -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.
@@ -752,12 +752,20 @@ 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

if index_algorithms.key?(options[:algorithm])
algorithm = index_algorithms[options[:algorithm]]
elsif options[:algorithm].present?
raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
end

using = "USING #{options[:using]}" if options[:using].present?

if supports_partial_index?
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
end
@@ -772,6 +780,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 +791,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 = {})
@@ -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
@@ -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.
@@ -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:
@@ -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
@@ -21,30 +21,37 @@ 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)

assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :coyp)
end

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?)
@@ -21,30 +21,37 @@ 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)

assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :coyp)
end

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?)
@@ -29,18 +29,27 @@ 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"))
assert_raise ArgumentError do
add_index(:people, :last_name, algorithm: :copy)
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')
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 2d33796

Please sign in to comment.