From e199dc1a570d4f0d9a07628268835bce5aab2732 Mon Sep 17 00:00:00 2001 From: Dan McClain Date: Mon, 25 Mar 2013 13:33:37 -0400 Subject: 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 --- activerecord/CHANGELOG.md | 12 ++++++++++++ .../connection_adapters/abstract/schema_statements.rb | 7 +++++-- .../connection_adapters/abstract_adapter.rb | 7 +++++++ .../connection_adapters/abstract_mysql_adapter.rb | 12 ++++++------ .../postgresql/schema_statements.rb | 8 ++------ .../connection_adapters/postgresql_adapter.rb | 4 ++++ .../test/cases/adapters/mysql/active_schema_test.rb | 19 +++++++++++-------- .../test/cases/adapters/mysql2/active_schema_test.rb | 19 +++++++++++-------- .../cases/adapters/postgresql/active_schema_test.rb | 16 +++++++++++----- 9 files changed, 69 insertions(+), 35 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 16460ec36d..a37147af2b 100644 --- a/activerecord/CHANGELOG.md +++ b/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. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 26cacbde37..35f1112798 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -752,11 +752,13 @@ 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, :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 @@ module ActiveRecord 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 @@ module ActiveRecord 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 = {}) diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 0f4ab68b61..1915c444ef 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -282,6 +282,13 @@ module ActiveRecord [] 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 diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 71f5c8fd29..ff3e9acfac 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -208,6 +208,10 @@ module ActiveRecord 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 @@ module ActiveRecord 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. diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 688bf1774b..0e1afbae8d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -410,12 +410,8 @@ module ActiveRecord 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: diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index d26b676335..46e41e6b48 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -433,6 +433,10 @@ module ActiveRecord true end + def index_algorithms + { concurrently: 'CONCURRENTLY' } + end + class StatementPool < ConnectionAdapters::StatementPool def initialize(connection, max) super diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 568fd45888..ca6478f6ac 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -21,30 +21,33 @@ class ActiveSchemaTest < ActiveRecord::TestCase 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?) diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index 273ef978bf..9b10d23465 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -21,30 +21,33 @@ class ActiveSchemaTest < ActiveRecord::TestCase 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?) diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index ac36d0e835..41e9bb912e 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -29,18 +29,24 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase 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?) -- cgit v1.2.3