Permalink
Browse files

Merge pull request #4956 from mhfs/pg_partial_indices

Add support for partial indices to PostgreSQL adapter
  • Loading branch information...
2 parents fc3fad8 + aaffc2a commit 3092324ef4e2f6f096adfbcf92597502b3ce91aa @tenderlove tenderlove committed Feb 10, 2012
@@ -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 @@
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 @@ def rename_column(table_name, column_name, new_column_name)
#
# 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 @@ def add_index_options(table_name, column_name, options = {})
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 @@ 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_name, index_type, index_columns, index_options]
end
def index_name_for_remove(table_name, options = {})
@@ -142,6 +142,11 @@ def supports_index_sort_order?
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 @@ def supports_index_sort_order?
true
end
+ def supports_partial_index?
+ true
+ end
+
class StatementPool < ConnectionAdapters::StatementPool
def initialize(connection, max)
super
@@ -885,8 +889,9 @@ def indexes(table_name, name = nil)
# 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 @@ def indexes(table, stream)
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 @@ def test_create_database_with_encoding
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 @@ def test_substitute_at
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 @@ def test_add_index
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 @@ def test_schema_dumps_index_columns_in_right_order
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 @@ def create_table(*args, &block)
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

0 comments on commit 3092324

Please sign in to comment.