schema_plus_pg_indexes 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +17 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +76 -0
- data/Rakefile +9 -0
- data/gemfiles/Gemfile.base +8 -0
- data/gemfiles/rails-4.2/Gemfile.base +3 -0
- data/gemfiles/rails-4.2/Gemfile.postgresql +10 -0
- data/lib/schema_plus_pg_indexes.rb +11 -0
- data/lib/schema_plus_pg_indexes/active_record/connection_adapters/index_definition.rb +49 -0
- data/lib/schema_plus_pg_indexes/middleware/migration.rb +23 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/dumper.rb +55 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/migration.rb +83 -0
- data/lib/schema_plus_pg_indexes/middleware/postgresql/query.rb +130 -0
- data/lib/schema_plus_pg_indexes/version.rb +3 -0
- data/schema_dev.yml +7 -0
- data/schema_plus_pg_indexes.gemspec +30 -0
- data/spec/deprecation_spec.rb +62 -0
- data/spec/index_definition_spec.rb +135 -0
- data/spec/index_spec.rb +121 -0
- data/spec/named_schema_spec.rb +60 -0
- data/spec/schema_dumper_spec.rb +166 -0
- data/spec/spec_helper.rb +34 -0
- data/spec/support/matchers/have_index.rb +60 -0
- metadata +203 -0
data/schema_dev.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'schema_plus_pg_indexes/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "schema_plus_pg_indexes"
|
8
|
+
spec.version = SchemaPlusPgIndexes::VERSION
|
9
|
+
spec.authors = ["ronen barzel"]
|
10
|
+
spec.email = ["ronen@barzel.org"]
|
11
|
+
spec.summary = %q{Adds support in ActiveRecord for PostgreSQL index expressions and operator classes, as well as a shorthand for case-insensitive indexes}
|
12
|
+
spec.homepage = "https://github.com/SchemaPlus/schema_plus_pg_indexes"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_dependency "activerecord", "~> 4.2"
|
21
|
+
spec.add_dependency "schema_monkey", "~> 0.3"
|
22
|
+
spec.add_dependency "schema_plus_indexes", "~> 0.1"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0.0"
|
27
|
+
spec.add_development_dependency "schema_dev", "~> 2.0"
|
28
|
+
spec.add_development_dependency "simplecov"
|
29
|
+
spec.add_development_dependency "simplecov-gem-profile"
|
30
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Deprecations' do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class User < ::ActiveRecord::Base ; end
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:migration) { ::ActiveRecord::Migration }
|
10
|
+
|
11
|
+
context "on table creation" do
|
12
|
+
it "deprecates :conditions" do
|
13
|
+
where = "((login)::text ~~ '%xyz'::text)"
|
14
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/conditions.*where/)
|
15
|
+
create_table User, :login => { index: { conditions: where } }
|
16
|
+
index = User.indexes.first
|
17
|
+
expect(index.where).to eq where
|
18
|
+
end
|
19
|
+
|
20
|
+
it "deprecates :kind" do
|
21
|
+
using = :hash
|
22
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/kind.*using/)
|
23
|
+
create_table User, :login => { index: { kind: using } }
|
24
|
+
index = User.indexes.first
|
25
|
+
expect(index.using).to eq using
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
context "on IndexDefinition object" do
|
30
|
+
|
31
|
+
it "deprecates #conditions" do
|
32
|
+
where = "((login)::text ~~ '%xyz'::text)"
|
33
|
+
create_table User, :login => { index: { where: where } }
|
34
|
+
index = User.indexes.first
|
35
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/conditions.*where/)
|
36
|
+
expect(index.where).to eq where # sanity check
|
37
|
+
expect(index.conditions).to eq index.where
|
38
|
+
end
|
39
|
+
|
40
|
+
it "deprecates #kind" do
|
41
|
+
using = :hash
|
42
|
+
create_table User, :login => { index: { using: using } }
|
43
|
+
index = User.indexes.first
|
44
|
+
expect(ActiveSupport::Deprecation).to receive(:warn).with(/kind.*using/)
|
45
|
+
expect(index.using).to eq using # sanity check
|
46
|
+
expect(index.kind).to eq index.using
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def create_table(model, columns_with_options)
|
53
|
+
migration.suppress_messages do
|
54
|
+
migration.create_table model.table_name, :force => true do |t|
|
55
|
+
columns_with_options.each_pair do |column, options|
|
56
|
+
t.send :string, column, options
|
57
|
+
end
|
58
|
+
end
|
59
|
+
model.reset_column_information
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe "Index definition" do
|
5
|
+
|
6
|
+
let(:migration) { ::ActiveRecord::Migration }
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
define_schema do
|
10
|
+
create_table :users, :force => true do |t|
|
11
|
+
t.string :login
|
12
|
+
t.datetime :deleted_at
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :posts, :force => true do |t|
|
16
|
+
t.text :body
|
17
|
+
t.integer :user_id
|
18
|
+
t.integer :author_id
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
class User < ::ActiveRecord::Base ; end
|
23
|
+
class Post < ::ActiveRecord::Base ; end
|
24
|
+
end
|
25
|
+
|
26
|
+
around(:each) do |example|
|
27
|
+
migration.suppress_messages do
|
28
|
+
example.run
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
after(:each) do
|
33
|
+
migration.remove_index :users, :name => 'users_login_index' if migration.index_name_exists? :users, 'users_login_index', true
|
34
|
+
end
|
35
|
+
|
36
|
+
context "when case insensitive is added" do
|
37
|
+
|
38
|
+
before(:each) do
|
39
|
+
migration.execute "CREATE INDEX users_login_index ON users(LOWER(login))"
|
40
|
+
User.reset_column_information
|
41
|
+
@index = User.indexes.detect { |i| i.expression =~ /lower\(\(login\)::text\)/i }
|
42
|
+
end
|
43
|
+
|
44
|
+
it "is included in User.indexes" do
|
45
|
+
expect(@index).not_to be_nil
|
46
|
+
end
|
47
|
+
|
48
|
+
it "is not case_sensitive" do
|
49
|
+
expect(@index).not_to be_case_sensitive
|
50
|
+
end
|
51
|
+
|
52
|
+
it "defines expression" do
|
53
|
+
expect(@index.expression).to eq("lower((login)::text)")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "doesn't define where" do
|
57
|
+
expect(@index.where).to be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
context "when index contains expression" do
|
64
|
+
before(:each) do
|
65
|
+
migration.execute "CREATE INDEX users_login_index ON users (extract(EPOCH from deleted_at)) WHERE deleted_at IS NULL"
|
66
|
+
User.reset_column_information
|
67
|
+
@index = User.indexes.detect { |i| i.expression.present? }
|
68
|
+
end
|
69
|
+
|
70
|
+
it "exists" do
|
71
|
+
expect(@index).not_to be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "doesnt have columns defined" do
|
75
|
+
expect(@index.columns).to be_empty
|
76
|
+
end
|
77
|
+
|
78
|
+
it "is case_sensitive" do
|
79
|
+
expect(@index).to be_case_sensitive
|
80
|
+
end
|
81
|
+
|
82
|
+
it "defines expression" do
|
83
|
+
expect(@index.expression).to eq("date_part('epoch'::text, deleted_at)")
|
84
|
+
end
|
85
|
+
|
86
|
+
it "defines where" do
|
87
|
+
expect(@index.where).to eq("(deleted_at IS NULL)")
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
context "when index has a non-btree type" do
|
93
|
+
before(:each) do
|
94
|
+
migration.execute "CREATE INDEX users_login_index ON users USING hash(login)"
|
95
|
+
User.reset_column_information
|
96
|
+
@index = User.indexes.detect { |i| i.name == "users_login_index" }
|
97
|
+
end
|
98
|
+
|
99
|
+
it "exists" do
|
100
|
+
expect(@index).not_to be_nil
|
101
|
+
end
|
102
|
+
|
103
|
+
it "defines using" do
|
104
|
+
expect(@index.using).to eq(:hash)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "does not define expression" do
|
108
|
+
expect(@index.expression).to be_nil
|
109
|
+
end
|
110
|
+
|
111
|
+
it "does not define order" do
|
112
|
+
expect(@index.orders).to be_blank
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "equality" do
|
117
|
+
|
118
|
+
it "returns true when case sensitivity are the same" do
|
119
|
+
expect(ActiveRecord::ConnectionAdapters::IndexDefinition.new("table", "column", case_sensitive: true)).to eq ActiveRecord::ConnectionAdapters::IndexDefinition.new("table", "column", case_sensitive: true)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "returns false when case sensitivity are the different" do
|
123
|
+
expect(ActiveRecord::ConnectionAdapters::IndexDefinition.new("table", "column", case_sensitive: true)).not_to eq ActiveRecord::ConnectionAdapters::IndexDefinition.new("table", "column", case_sensitive: false)
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
protected
|
130
|
+
def index_definition(column_names)
|
131
|
+
User.indexes.detect { |index| index.columns == Array(column_names) }
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
end
|
data/spec/index_spec.rb
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "index" do
|
4
|
+
|
5
|
+
let(:migration) { ::ActiveRecord::Migration }
|
6
|
+
let(:connection) { ::ActiveRecord::Base.connection }
|
7
|
+
|
8
|
+
describe "add_index" do
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
connection.tables.each do |table| connection.drop_table table, cascade: true end
|
12
|
+
|
13
|
+
define_schema do
|
14
|
+
create_table :users, :force => true do |t|
|
15
|
+
t.string :login
|
16
|
+
t.text :address
|
17
|
+
t.datetime :deleted_at
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :posts, :force => true do |t|
|
21
|
+
t.text :body
|
22
|
+
t.integer :user_id
|
23
|
+
t.integer :author_id
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
class User < ::ActiveRecord::Base ; end
|
28
|
+
class Post < ::ActiveRecord::Base ; end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
after(:each) do
|
33
|
+
migration.suppress_messages do
|
34
|
+
migration.remove_index(:users, :name => @index.name) if (@index ||= nil)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "extra features" do
|
39
|
+
|
40
|
+
it "should assign expression, where and using" do
|
41
|
+
add_index(:users, :expression => "USING hash (upper(login)) WHERE deleted_at IS NULL", :name => 'users_login_index')
|
42
|
+
@index = User.indexes.detect { |i| i.expression.present? }
|
43
|
+
expect(@index.expression).to eq("upper((login)::text)")
|
44
|
+
expect(@index.where).to eq("(deleted_at IS NULL)")
|
45
|
+
expect(@index.using).to eq(:hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should allow to specify expression, where and using separately" do
|
49
|
+
add_index(:users, :using => "hash", :expression => "upper(login)", :where => "deleted_at IS NULL", :name => 'users_login_index')
|
50
|
+
@index = User.indexes.detect { |i| i.expression.present? }
|
51
|
+
expect(@index.expression).to eq("upper((login)::text)")
|
52
|
+
expect(@index.where).to eq("(deleted_at IS NULL)")
|
53
|
+
expect(@index.using).to eq(:hash)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should assign operator_class" do
|
57
|
+
add_index(:users, :login, :operator_class => 'varchar_pattern_ops')
|
58
|
+
expect(index_for(:login).operator_classes).to eq({"login" => 'varchar_pattern_ops'})
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should assign multiple operator_classes" do
|
62
|
+
add_index(:users, [:login, :address], :operator_class => {:login => 'varchar_pattern_ops', :address => 'text_pattern_ops'})
|
63
|
+
expect(index_for([:login, :address]).operator_classes).to eq({"login" => 'varchar_pattern_ops', "address" => 'text_pattern_ops'})
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should allow to specify actual expression only" do
|
67
|
+
add_index(:users, :expression => "upper(login)", :name => 'users_login_index')
|
68
|
+
@index = User.indexes.detect { |i| i.name == 'users_login_index' }
|
69
|
+
expect(@index.expression).to eq("upper((login)::text)")
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should raise if no column given and expression is missing" do
|
73
|
+
expect { add_index(:users, :name => 'users_login_index') }.to raise_error(ArgumentError, /expression/)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should raise if expression without name is given" do
|
77
|
+
expect { add_index(:users, :expression => "upper(login)") }.to raise_error(ArgumentError, /name/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise if expression is given and case_sensitive is false" do
|
81
|
+
expect { add_index(:users, :name => 'users_login_index', :expression => "upper(login)", :case_sensitive => false) }.to raise_error(ArgumentError, /use LOWER/i)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context "create table" do
|
87
|
+
it "defines index with expression only" do
|
88
|
+
define_schema do
|
89
|
+
create_table :users, :force => true do |t|
|
90
|
+
t.string :login
|
91
|
+
t.index :expression => "upper(login)", name: "no_column"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
expect(User.indexes.first.expression).to eq("upper((login)::text)")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
def index_for(column_names)
|
101
|
+
@index = User.indexes.detect { |i| i.columns == Array(column_names).collect(&:to_s) }
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
protected
|
107
|
+
def add_index(*args)
|
108
|
+
migration.suppress_messages do
|
109
|
+
migration.add_index(*args)
|
110
|
+
end
|
111
|
+
User.reset_column_information
|
112
|
+
end
|
113
|
+
|
114
|
+
def remove_index(*args)
|
115
|
+
migration.suppress_messages do
|
116
|
+
migration.remove_index(*args)
|
117
|
+
end
|
118
|
+
User.reset_column_information
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "with multiple schemas" do
|
4
|
+
def connection
|
5
|
+
ActiveRecord::Base.connection
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
newdb = case connection.adapter_name
|
10
|
+
when /^mysql/i then "CREATE SCHEMA IF NOT EXISTS schema_plus_pg_indexes_test2"
|
11
|
+
when /^postgresql/i then "CREATE SCHEMA schema_plus_pg_indexes_test2"
|
12
|
+
when /^sqlite/i then "ATTACH ':memory:' AS schema_plus_pg_indexes_test2"
|
13
|
+
end
|
14
|
+
begin
|
15
|
+
ActiveRecord::Base.connection.execute newdb
|
16
|
+
rescue ActiveRecord::StatementInvalid => e
|
17
|
+
raise unless e.message =~ /already exists/
|
18
|
+
end
|
19
|
+
|
20
|
+
class User < ::ActiveRecord::Base ; end
|
21
|
+
end
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
define_schema do
|
25
|
+
create_table :users, :force => true do |t|
|
26
|
+
t.string :login
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
connection.execute 'DROP TABLE IF EXISTS schema_plus_pg_indexes_test2.users'
|
31
|
+
connection.execute 'CREATE TABLE schema_plus_pg_indexes_test2.users (id ' + case connection.adapter_name
|
32
|
+
when /^mysql/i then "integer primary key auto_increment"
|
33
|
+
when /^postgresql/i then "serial primary key"
|
34
|
+
when /^sqlite/i then "integer primary key autoincrement"
|
35
|
+
end + ", login varchar(255))"
|
36
|
+
end
|
37
|
+
|
38
|
+
context "with indexes in each schema" do
|
39
|
+
before(:each) do
|
40
|
+
connection.execute 'CREATE INDEX ' + case connection.adapter_name
|
41
|
+
when /^mysql/i then "index_users_on_login ON schema_plus_pg_indexes_test2.users"
|
42
|
+
when /^postgresql/i then "index_users_on_login ON schema_plus_pg_indexes_test2.users"
|
43
|
+
when /^sqlite/i then "schema_plus_pg_indexes_test2.index_users_on_login ON users"
|
44
|
+
end + " (login)"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not find indexes in other schema" do
|
48
|
+
User.reset_column_information
|
49
|
+
expect(User.indexes).to be_empty
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should find index in current schema" do
|
53
|
+
connection.execute 'CREATE INDEX index_users_on_login ON users (login)'
|
54
|
+
User.reset_column_information
|
55
|
+
expect(User.indexes.map(&:name)).to eq(['index_users_on_login'])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe "Schema dump" do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
ActiveRecord::Migration.suppress_messages do
|
8
|
+
ActiveRecord::Schema.define do
|
9
|
+
connection.tables.each do |table| drop_table table, :cascade => true end
|
10
|
+
|
11
|
+
create_table :users, :force => true do |t|
|
12
|
+
t.string :login
|
13
|
+
t.datetime :deleted_at
|
14
|
+
t.integer :first_post_id
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :posts, :force => true do |t|
|
18
|
+
t.text :body
|
19
|
+
t.integer :user_id
|
20
|
+
t.integer :first_comment_id
|
21
|
+
t.string :string_no_default
|
22
|
+
t.integer :short_id
|
23
|
+
t.string :str_short
|
24
|
+
t.integer :integer_col
|
25
|
+
t.float :float_col
|
26
|
+
t.decimal :decimal_col
|
27
|
+
t.datetime :datetime_col
|
28
|
+
t.timestamp :timestamp_col
|
29
|
+
t.time :time_col
|
30
|
+
t.date :date_col
|
31
|
+
t.binary :binary_col
|
32
|
+
t.boolean :boolean_col
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :comments, :force => true do |t|
|
36
|
+
t.text :body
|
37
|
+
t.integer :post_id
|
38
|
+
t.integer :commenter_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
class ::User < ActiveRecord::Base ; end
|
43
|
+
class ::Post < ActiveRecord::Base ; end
|
44
|
+
class ::Comment < ActiveRecord::Base ; end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "index extras" do
|
48
|
+
|
49
|
+
it "should define case insensitive index" do
|
50
|
+
with_index Post, [:body, :string_no_default], :case_sensitive => false do
|
51
|
+
expect(dump_posts).to match(/"body".*index: {.*with:.*string_no_default.*case_sensitive: false/)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should define index with type cast" do
|
56
|
+
with_index Post, [:integer_col], :name => "index_with_type_cast", :expression => "LOWER(integer_col::text)" do
|
57
|
+
expect(dump_posts).to include(%q{t.index name: "index_with_type_cast", expression: "lower((integer_col)::text)"})
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "should define case insensitive index with mixed ids and strings" do
|
63
|
+
with_index Post, [:user_id, :str_short, :short_id, :body], :case_sensitive => false do
|
64
|
+
expect(dump_posts).to match(/user_id.*index: {.* with: \["str_short", "short_id", "body"\], case_sensitive: false}/)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
[:integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean].each do |col_type|
|
69
|
+
col_name = "#{col_type}_col"
|
70
|
+
it "should define case insensitive index that includes an #{col_type}" do
|
71
|
+
with_index Post, [:user_id, :str_short, col_name, :body], :case_sensitive => false do
|
72
|
+
expect(dump_posts).to match(/user_id.*index: {.* with: \["str_short", "#{col_name}", "body"\], case_sensitive: false}/)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should define where" do
|
78
|
+
with_index Post, :user_id, :name => "posts_user_id_index", :where => "user_id IS NOT NULL" do
|
79
|
+
expect(dump_posts).to match(/user_id.*index: {.*where: "\(user_id IS NOT NULL\)"}/)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should define expression" do
|
84
|
+
with_index Post, :name => "posts_freaky_index", :expression => "USING hash (least(id, user_id))" do
|
85
|
+
expect(dump_posts).to include(%q{t.index name: "posts_freaky_index", using: :hash, expression: "LEAST(id, user_id)"})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should define operator_class" do
|
90
|
+
with_index Post, :body, :operator_class => 'text_pattern_ops' do
|
91
|
+
expect(dump_posts).to match(/body.*index:.*operator_class: "text_pattern_ops"/)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should define multi-column operator classes " do
|
96
|
+
with_index Post, [:body, :string_no_default], :operator_class => {body: 'text_pattern_ops', string_no_default: 'varchar_pattern_ops' } do
|
97
|
+
expect(dump_posts).to match(/body.*index:.*operator_class: {"body"=>"text_pattern_ops", "string_no_default"=>"varchar_pattern_ops"}/)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should dump unique: true with expression (Issue #142)" do
|
102
|
+
with_index Post, :name => "posts_user_body_index", :unique => true, :expression => "BTRIM(LOWER(body))" do
|
103
|
+
expect(dump_posts).to include(%q{t.index name: "posts_user_body_index", unique: true, expression: "btrim(lower(body))"})
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "should not define :case_sensitive => false with non-trivial expression" do
|
109
|
+
with_index Post, :name => "posts_user_body_index", :expression => "BTRIM(LOWER(body))" do
|
110
|
+
expect(dump_posts).to include(%q{t.index name: "posts_user_body_index", expression: "btrim(lower(body))"})
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should define using" do
|
115
|
+
with_index Post, :name => "posts_body_index", :expression => "USING hash (body)" do
|
116
|
+
expect(dump_posts).to match(/body.*index:.*using: :hash/)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it "should not include index order for non-ordered index types" do
|
121
|
+
with_index Post, :user_id, :using => :hash do
|
122
|
+
expect(dump_posts).to match(/user_id.*index:.*using: :hash/)
|
123
|
+
expect(dump_posts).not_to match(%r{order})
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
protected
|
130
|
+
|
131
|
+
def with_index(*args)
|
132
|
+
options = args.extract_options!
|
133
|
+
model, columns = args
|
134
|
+
ActiveRecord::Migration.suppress_messages do
|
135
|
+
ActiveRecord::Migration.add_index(model.table_name, columns, options)
|
136
|
+
end
|
137
|
+
model.reset_column_information
|
138
|
+
begin
|
139
|
+
yield
|
140
|
+
ensure
|
141
|
+
ActiveRecord::Migration.suppress_messages do
|
142
|
+
ActiveRecord::Migration.remove_index(model.table_name, :name => determine_index_name(model, columns, options))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def determine_index_name(model, columns, options)
|
148
|
+
name = columns[:name] if columns.is_a?(Hash)
|
149
|
+
name ||= options[:name]
|
150
|
+
name ||= model.indexes.detect { |index| index.table == model.table_name.to_s && index.columns.sort == Array(columns).collect(&:to_s).sort }.name
|
151
|
+
name
|
152
|
+
end
|
153
|
+
|
154
|
+
def dump_schema(opts={})
|
155
|
+
stream = StringIO.new
|
156
|
+
ActiveRecord::SchemaDumper.ignore_tables = Array.wrap(opts[:ignore]) || []
|
157
|
+
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
158
|
+
stream.string
|
159
|
+
end
|
160
|
+
|
161
|
+
def dump_posts
|
162
|
+
dump_schema(:ignore => %w[users comments])
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|