mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #4956 from mhfs/pg_partial_indices
Add support for partial indices to PostgreSQL adapter
This commit is contained in:
commit
3092324ef4
11 changed files with 77 additions and 5 deletions
|
@ -1,5 +1,18 @@
|
|||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Added support for partial indices to PostgreSQL adapter
|
||||
|
||||
The `add_index` method now supports a `where` option that receives a
|
||||
string with the partial index criteria.
|
||||
|
||||
add_index(:accounts, :code, :where => "active")
|
||||
|
||||
Generates
|
||||
|
||||
CREATE INDEX index_accounts_on_code ON accounts(code) WHERE active
|
||||
|
||||
*Marcelo Silveira*
|
||||
|
||||
* Implemented ActiveRecord::Relation#none method
|
||||
|
||||
The `none` method returns a chainable relation with zero records
|
||||
|
|
|
@ -6,7 +6,7 @@ require 'bigdecimal/util'
|
|||
|
||||
module ActiveRecord
|
||||
module ConnectionAdapters #:nodoc:
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders) #:nodoc:
|
||||
class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where) #:nodoc:
|
||||
end
|
||||
|
||||
# Abstract representation of a column definition. Instances of this type
|
||||
|
|
|
@ -381,9 +381,16 @@ module ActiveRecord
|
|||
#
|
||||
# Note: mysql doesn't yet support index order (it accepts the syntax but ignores it)
|
||||
#
|
||||
# ====== Creating a partial index
|
||||
# add_index(:accounts, [:branch_id, :party_id], :unique => true, :where => "active")
|
||||
# generates
|
||||
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
|
||||
#
|
||||
# Note: only supported by PostgreSQL
|
||||
#
|
||||
def add_index(table_name, column_name, options = {})
|
||||
index_name, index_type, index_columns = 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_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}"
|
||||
end
|
||||
|
||||
# Remove the given index from the table.
|
||||
|
@ -581,6 +588,9 @@ module ActiveRecord
|
|||
if Hash === options # legacy support, since this param was a string
|
||||
index_type = options[:unique] ? "UNIQUE" : ""
|
||||
index_name = options[:name].to_s if options.key?(:name)
|
||||
if supports_partial_index?
|
||||
index_options = options[:where] ? " WHERE #{options[:where]}" : ""
|
||||
end
|
||||
else
|
||||
index_type = options
|
||||
end
|
||||
|
@ -593,7 +603,7 @@ module ActiveRecord
|
|||
end
|
||||
index_columns = quoted_columns_for_index(column_names, options).join(", ")
|
||||
|
||||
[index_name, index_type, index_columns]
|
||||
[index_name, index_type, index_columns, index_options]
|
||||
end
|
||||
|
||||
def index_name_for_remove(table_name, options = {})
|
||||
|
|
|
@ -142,6 +142,11 @@ module ActiveRecord
|
|||
false
|
||||
end
|
||||
|
||||
# Does this adapter support partial indices?
|
||||
def supports_partial_index?
|
||||
false
|
||||
end
|
||||
|
||||
# Does this adapter support explain? As of this writing sqlite3,
|
||||
# mysql2, and postgresql are the only ones that do.
|
||||
def supports_explain?
|
||||
|
|
|
@ -302,6 +302,10 @@ module ActiveRecord
|
|||
true
|
||||
end
|
||||
|
||||
def supports_partial_index?
|
||||
true
|
||||
end
|
||||
|
||||
class StatementPool < ConnectionAdapters::StatementPool
|
||||
def initialize(connection, max)
|
||||
super
|
||||
|
@ -885,8 +889,9 @@ module ActiveRecord
|
|||
# add info on sort order for columns (only desc order is explicitly specified, asc is the default)
|
||||
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]
|
||||
|
||||
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
|
||||
column_names.empty? ? nil : IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where)
|
||||
end.compact
|
||||
end
|
||||
|
||||
|
|
|
@ -197,6 +197,8 @@ HEADER
|
|||
index_orders = (index.orders || {})
|
||||
statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
|
||||
|
||||
statement_parts << (':where => ' + index.where.inspect) if index.where
|
||||
|
||||
' ' + statement_parts.join(', ')
|
||||
end
|
||||
|
||||
|
|
|
@ -21,6 +21,18 @@ class PostgresqlActiveSchemaTest < ActiveRecord::TestCase
|
|||
assert_equal %(CREATE DATABASE "aimonetti" ENCODING = 'latin1'), create_database(:aimonetti, :encoding => :latin1)
|
||||
end
|
||||
|
||||
def test_add_index
|
||||
# add_index calls index_name_exists? which can't work since execute is stubbed
|
||||
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) do |*|
|
||||
false
|
||||
end
|
||||
|
||||
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'")
|
||||
|
||||
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:remove_method, :index_name_exists?)
|
||||
end
|
||||
|
||||
private
|
||||
def method_missing(method_symbol, *arguments)
|
||||
ActiveRecord::Base.connection.send(method_symbol, *arguments)
|
||||
|
|
|
@ -179,6 +179,12 @@ module ActiveRecord
|
|||
assert_equal Arel.sql('$2'), bind
|
||||
end
|
||||
|
||||
def test_partial_index
|
||||
@connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
|
||||
index = @connection.indexes('ex').find { |idx| idx.name == 'partial' }
|
||||
assert_equal "(number > 100)", index.where
|
||||
end
|
||||
|
||||
private
|
||||
def insert(ctx, data)
|
||||
binds = data.map { |name, value|
|
||||
|
|
|
@ -171,6 +171,15 @@ module ActiveRecord
|
|||
end
|
||||
end
|
||||
|
||||
def test_add_partial_index
|
||||
skip 'only on pg' unless current_adapter?(:PostgreSQLAdapter)
|
||||
|
||||
connection.add_index("testings", "last_name", :where => "first_name = 'john doe'")
|
||||
assert connection.index_exists?("testings", "last_name")
|
||||
|
||||
connection.remove_index("testings", "last_name")
|
||||
assert !connection.index_exists?("testings", "last_name")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -185,6 +185,15 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
assert_equal 'add_index "companies", ["firm_id", "type", "rating", "ruby_type"], :name => "company_index"', index_definition
|
||||
end
|
||||
|
||||
def test_schema_dumps_partial_indices
|
||||
index_definition = standard_dump.split(/\n/).grep(/add_index.*company_partial_index/).first.strip
|
||||
if current_adapter?(:PostgreSQLAdapter)
|
||||
assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index", :where => "(rating > 10)"', index_definition
|
||||
else
|
||||
assert_equal 'add_index "companies", ["firm_id", "type"], :name => "company_partial_index"', index_definition
|
||||
end
|
||||
end
|
||||
|
||||
def test_schema_dump_should_honor_nonstandard_primary_keys
|
||||
output = standard_dump
|
||||
match = output.match(%r{create_table "movies"(.*)do})
|
||||
|
|
|
@ -175,6 +175,7 @@ ActiveRecord::Schema.define do
|
|||
end
|
||||
|
||||
add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index"
|
||||
add_index :companies, [:firm_id, :type], :name => "company_partial_index", :where => "rating > 10"
|
||||
|
||||
create_table :computers, :force => true do |t|
|
||||
t.integer :developer, :null => false
|
||||
|
|
Loading…
Reference in a new issue