1
0
Fork 0
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:
Aaron Patterson 2012-02-10 10:23:42 -08:00
commit 3092324ef4
11 changed files with 77 additions and 5 deletions

View file

@ -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

View file

@ -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

View file

@ -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 = {})

View file

@ -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?

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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|

View file

@ -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

View file

@ -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})

View file

@ -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