Skip to content
Browse files

add ability to specify index_type and where in creating / dumping ind…

…exes.

add extension point for enhancing column_specs
  • Loading branch information...
1 parent c0b4e54 commit ff7ee605cdcd5479f377b5908bc3c484e04a938b @kbrock committed Sep 25, 2012
View
4 CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.0.9
+
+Adds more robust index types with add_index options :index_type and :where.
+
## 0.0.8
Fixes add and change column
View
19 README.md
@@ -253,6 +253,25 @@ user_arel = User.arel_table
User.where(user_arel[:ip_address].contained_witin('127.0.0.1/24')).to_sql
# => SELECT \"users\".* FROM \"users\" WHERE \"users\".\"ip_address\" << '127.0.0.1/24'
```
+
+## Indexes
+
+### Index types
+
+Postgres\_ext allows you to specify an index type at index creation.
+
+```ruby
+add_index :table_name, :column, :index_type => :gin
+```
+
+### Where clauses
+
+Postgres\_ext allows you to specify a where clause at index creation.
+
+```ruby
+add_index :table_name, :column, :where => 'column < 50'
+```
+
## Authors
Dan McClain [twitter](http://twitter.com/_danmcclain) [github](http://github.com/danmcclain)
View
60 lib/postgres_ext/active_record/connection_adapters/postgres_adapter.rb
@@ -4,6 +4,11 @@
module ActiveRecord
module ConnectionAdapters
+ # class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :index_type)
+ class IndexDefinition
+ attr_accessor :index_type, :where
+ end
+
class PostgreSQLColumn
include PgArrayParser
attr_accessor :array
@@ -179,6 +184,15 @@ def add_column_options!(sql, options)
super
end
+ def add_index(table_name, column_name, options = {})
+ index_name, unique, index_columns, _ = add_index_options(table_name, column_name, options)
+ if options.is_a? Hash
+ index_type = options[:index_type] ? " USING #{options[:index_type]} " : ""
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
+ end
+ execute "CREATE #{unique} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}#{index_type}(#{index_columns})#{index_options}"
+ end
+
def change_table(table_name, options = {})
if supports_bulk_alter? && options[:bulk]
recorder = ActiveRecord::Migration::CommandRecorder.new(self)
@@ -242,6 +256,52 @@ def quote_with_extended_types(value, column = nil)
end
alias_method_chain :quote, :extended_types
+ # this is based upon rails 4 changes to include different index methods
+ # Returns an array of indexes for the given table.
+ def indexes(table_name, name = nil)
+ result = select_rows(<<-SQL, name)
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ FROM pg_class t
+ INNER JOIN pg_index d ON t.oid = d.indrelid
+ INNER JOIN pg_class i ON d.indexrelid = i.oid
+ WHERE i.relkind = 'i'
+ AND d.indisprimary = 'f'
+ AND t.relname = '#{table_name}'
+ AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
+ ORDER BY i.relname
+ SQL
+ result.map do |row|
+ index_name = row[0]
+ unique = row[1] == 't'
+ indkey = row[2].split(" ")
+ inddef = row[3]
+ oid = row[4]
+
+ columns = Hash[select_rows(<<-SQL, "Columns for index #{row[0]} on #{table_name}")]
+ SELECT a.attnum::text, a.attname
+ FROM pg_attribute a
+ WHERE a.attrelid = #{oid}
+ AND a.attnum IN (#{indkey.join(",")})
+ SQL
+ column_names = columns.values_at(*indkey).compact
+
+ # 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]}] : {}
+ #changed from rails 3.2
+ where = inddef.scan(/WHERE (.+)$/).flatten[0]
+ index_type = inddef.scan(/USING (.+?) /).flatten[0].to_sym
+ if column_names.present?
+ index_def = IndexDefinition.new(table_name, index_name, unique, column_names, [], orders)
+ index_def.where = where
+ index_def.index_type = index_type if index_type && index_type != :btree
+ index_def
+ # else nil
+ end
+ #/changed
+ end.compact
+ end
+
private
def ipaddr_to_string(value)
View
45 lib/postgres_ext/active_record/schema_dumper.rb
@@ -2,6 +2,11 @@
module ActiveRecord
class SchemaDumper
+ VALID_COLUMN_SPEC_KEYS = [:name, :limit, :precision, :scale, :default, :null, :array]
+ def self.valid_column_spec_keys
+ VALID_COLUMN_SPEC_KEYS
+ end
+
private
def table(table, stream)
columns = @connection.columns(table)
@@ -30,11 +35,13 @@ def table(table, stream)
column_specs = columns.map do |column|
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
next if column.name == pk
- column_spec(column)
+ spec = column_spec(column)
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
+ spec
end.compact
# find all migration keys used in this table
- keys = [:name, :limit, :precision, :scale, :default, :null, :array] & column_specs.map{ |k| k.keys }.flatten
+ keys = self.class.valid_column_spec_keys & column_specs.map{ |k| k.keys }.flatten
# figure out the lengths for each column based on above keys
lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
@@ -73,6 +80,37 @@ def table(table, stream)
stream
end
+ #mostly rails 3.2 code
+ def indexes(table, stream)
+ if (indexes = @connection.indexes(table)).any?
+ add_index_statements = indexes.map do |index|
+ statement_parts = [
+ ('add_index ' + index.table.inspect),
+ index.columns.inspect,
+ (':name => ' + index.name.inspect),
+ ]
+ statement_parts << ':unique => true' if index.unique
+
+ index_lengths = (index.lengths || []).compact
+ statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
+
+ index_orders = (index.orders || {})
+ statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
+
+ # changed from rails 2.3
+ statement_parts << (':where => ' + index.where.inspect) if index.where
+ statement_parts << (':index_type => ' + index.index_type.inspect) if index.index_type
+ # /changed
+
+ ' ' + statement_parts.join(', ')
+ end
+
+ stream.puts add_index_statements.sort.join("\n")
+ stream.puts
+ end
+ end
+
+ #mostly rails 3.2 code (pulled out of table method)
def column_spec(column)
spec = {}
spec[:name] = column.name.inspect
@@ -89,8 +127,9 @@ def column_spec(column)
spec[:scale] = column.scale.inspect if column.scale
spec[:null] = 'false' unless column.null
spec[:default] = default_string(column.default) if column.has_default?
+ # changed from rails 3.2 code
spec[:array] = 'true' if column.respond_to?(:array) && column.array
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
+ # /changed
spec
end
end
View
9 postgres_ext.gemspec
@@ -16,7 +16,7 @@ Gem::Specification.new do |gem|
gem.version = PostgresExt::VERSION
gem.add_dependency 'activerecord', '~> 3.2.0'
- gem.add_dependency 'pg_array_parser', '~> 0.0.3'
+ gem.add_dependency 'pg_array_parser', '~> 0.0.8'
gem.add_development_dependency 'rails', '~> 3.2.0'
gem.add_development_dependency 'rspec-rails', '~> 2.9.0'
@@ -27,8 +27,11 @@ Gem::Specification.new do |gem|
gem.add_development_dependency 'pg', '~> 0.13.2'
end
unless ENV['CI']
- gem.add_development_dependency 'debugger', '~> 1.1.2' if RUBY_VERSION == '1.9.3'
- gem.add_development_dependency 'ruby-debug' if RUBY_PLATFORM =~ /java/
+ if RUBY_PLATFORM =~ /java/
+ gem.add_development_dependency 'ruby-debug'
+ elsif RUBY_VERSION == '1.9.3'
+ gem.add_development_dependency 'debugger', '~> 1.1.2'
+ end
end
gem.add_development_dependency 'fivemat'
end
View
2 spec/dummy/config/environments/development.rb
@@ -1,5 +1,5 @@
Dummy::Application.configure do
- require 'debugger' if RUBY_VERSION == '1.9.3'
+ require 'debugger' if RUBY_VERSION == '1.9.3' && ! ENV['CI']
# Settings specified here will take precedence over those in config/application.rb
# In the development environment your application's code is reloaded on
View
22 spec/migrations/index_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe 'index migrations' do
+ let!(:connection) { ActiveRecord::Base.connection }
+ it 'creates special index' do
+ lambda do
+ connection.create_table :index_types do |t|
+ t.integer :col1, :array => true
+ t.integer :col2
+ end
+ connection.add_index(:index_types, :col1, :index_type => :gin)
+ connection.add_index(:index_types, :col2, :where => '(col2 > 50)')
+ end.should_not raise_exception
+
+ indexes = connection.indexes(:index_types)
+ index_1 = indexes.detect { |c| c.columns.map(&:to_s) == ['col1']}
+ index_2 = indexes.detect { |c| c.columns.map(&:to_s) == ['col2']}
+
+ index_1.index_type.to_s.should eq 'gin'
+ index_2.where.should match /col2 > 50/
+ end
+end
View
21 spec/schema_dumper/index_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe 'index schema dumper' do
+ let!(:connection) { ActiveRecord::Base.connection }
+ it 'correctly generates index statements' do
+ connection.create_table :index_types do |t|
+ t.integer :col1, :array => true
+ t.integer :col2
+ end
+ connection.add_index(:index_types, :col1, :index_type => :gin)
+ connection.add_index(:index_types, :col2, :where => '(col2 > 50)')
+
+ stream = StringIO.new
+ ActiveRecord::SchemaDumper.dump(connection, stream)
+ output = stream.string
+
+ output.should match /:index_type => :gin/
+ output.should_not match /:index_type => :btree/
+ output.should match /:where => "\(col2 > 50\)"/
+ end
+end
View
1 spec/spec_helper.rb
@@ -3,6 +3,7 @@
require File.expand_path('../dummy/config/environment.rb', __FILE__)
require 'rspec/rails'
+require 'rspec/autorun'
require 'bourne'
ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')

0 comments on commit ff7ee60

Please sign in to comment.
Something went wrong with that request. Please try again.