schema_plus_pg_indexes 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|