schema_plus 1.8.9 → 2.0.0.pre1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -4
- data/.travis.yml +1 -47
- data/CHANGELOG.md +0 -35
- data/README.md +73 -107
- data/Rakefile +7 -10
- data/TODO.md +51 -0
- data/gemfiles/Gemfile.base +2 -0
- data/lib/schema_column_plus.rb +7 -0
- data/lib/{schema_plus → schema_column_plus}/active_record/connection_adapters/column.rb +13 -11
- data/lib/schema_column_plus/middleware/model.rb +22 -0
- data/lib/schema_db_default.rb +13 -0
- data/lib/{schema_plus → schema_db_default}/active_record/attribute.rb +4 -4
- data/lib/schema_db_default/db_default.rb +17 -0
- data/lib/schema_db_default/middleware.rb +30 -0
- data/lib/schema_default_expr.rb +32 -0
- data/lib/schema_default_expr/active_record/connection_adapters/mysql_adapter.rb +17 -0
- data/lib/schema_default_expr/active_record/connection_adapters/postgresql_adapter.rb +18 -0
- data/lib/schema_default_expr/active_record/connection_adapters/sqlite3_adapter.rb +35 -0
- data/lib/schema_default_expr/middleware.rb +54 -0
- data/lib/schema_pg_enums.rb +6 -0
- data/lib/schema_pg_enums/active_record.rb +69 -0
- data/lib/schema_pg_enums/middleware.rb +23 -0
- data/lib/schema_plus.rb +17 -45
- data/lib/schema_plus/active_record/base.rb +6 -23
- data/lib/schema_plus/active_record/connection_adapters/abstract_adapter.rb +80 -181
- data/lib/schema_plus/active_record/connection_adapters/foreign_key_definition.rb +78 -99
- data/lib/schema_plus/active_record/connection_adapters/mysql_adapter.rb +34 -114
- data/lib/schema_plus/active_record/connection_adapters/postgresql_adapter.rb +16 -370
- data/lib/schema_plus/active_record/connection_adapters/schema_statements.rb +1 -67
- data/lib/schema_plus/active_record/connection_adapters/sqlite3_adapter.rb +18 -112
- data/lib/schema_plus/active_record/connection_adapters/table_definition.rb +14 -116
- data/lib/schema_plus/active_record/migration/command_recorder.rb +8 -59
- data/lib/schema_plus/middleware/dumper.rb +94 -0
- data/lib/schema_plus/middleware/migration.rb +167 -0
- data/lib/schema_plus/middleware/model.rb +17 -0
- data/lib/schema_plus/version.rb +1 -1
- data/lib/schema_plus_tables.rb +15 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/abstract_adapter.rb +20 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/mysql_adapter.rb +25 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/postgresql_adapter.rb +13 -0
- data/lib/schema_plus_tables/active_record/connection_adapters/sqlite3_adapter.rb +12 -0
- data/lib/schema_views.rb +16 -0
- data/lib/schema_views/active_record/connection_adapters/abstract_adapter.rb +41 -0
- data/lib/schema_views/active_record/connection_adapters/mysql_adapter.rb +30 -0
- data/lib/schema_views/active_record/connection_adapters/postgresql_adapter.rb +31 -0
- data/lib/schema_views/active_record/connection_adapters/sqlite3_adapter.rb +18 -0
- data/lib/schema_views/middleware.rb +47 -0
- data/schema_dev.yml +1 -31
- data/schema_plus.gemspec +11 -9
- data/spec/foreign_key_definition_spec.rb +7 -7
- data/spec/foreign_key_spec.rb +63 -48
- data/spec/migration_spec.rb +58 -203
- data/spec/named_schemas_spec.rb +5 -88
- data/spec/{column_spec.rb → schema_column_plus/column_spec.rb} +26 -48
- data/spec/schema_db_default/column_spec.rb +58 -0
- data/spec/{column_default_spec.rb → schema_default_expr/column_default_spec.rb} +1 -2
- data/spec/schema_default_expr/schema_dumper_spec.rb +116 -0
- data/spec/schema_dumper_spec.rb +22 -327
- data/spec/{enum_spec.rb → schema_pg_enums/enum_spec.rb} +1 -1
- data/spec/schema_pg_enums/schema_dumper_spec.rb +37 -0
- data/spec/schema_views/named_schemas_spec.rb +97 -0
- data/spec/{views_spec.rb → schema_views/views_spec.rb} +1 -1
- data/spec/spec_helper.rb +2 -1
- data/spec/support/matchers/reference.rb +11 -12
- metadata +104 -57
- data/gemfiles/rails-3.2/Gemfile.base +0 -3
- data/gemfiles/rails-3.2/Gemfile.mysql +0 -10
- data/gemfiles/rails-3.2/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-3.2/Gemfile.postgresql +0 -10
- data/gemfiles/rails-3.2/Gemfile.sqlite3 +0 -10
- data/gemfiles/rails-4.0/Gemfile.base +0 -3
- data/gemfiles/rails-4.0/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-4.0/Gemfile.postgresql +0 -10
- data/gemfiles/rails-4.0/Gemfile.sqlite3 +0 -10
- data/gemfiles/rails-4.1/Gemfile.base +0 -3
- data/gemfiles/rails-4.1/Gemfile.mysql2 +0 -10
- data/gemfiles/rails-4.1/Gemfile.postgresql +0 -10
- data/gemfiles/rails-4.1/Gemfile.sqlite3 +0 -10
- data/lib/schema_plus/active_record/column_options_handler.rb +0 -117
- data/lib/schema_plus/active_record/connection_adapters/index_definition.rb +0 -70
- data/lib/schema_plus/active_record/db_default.rb +0 -19
- data/lib/schema_plus/active_record/foreign_keys.rb +0 -137
- data/lib/schema_plus/active_record/schema_dumper.rb +0 -171
- data/lib/schema_plus/railtie.rb +0 -20
- data/spec/index_definition_spec.rb +0 -211
- data/spec/index_spec.rb +0 -249
@@ -1,171 +0,0 @@
|
|
1
|
-
require 'tsort'
|
2
|
-
|
3
|
-
module SchemaPlus
|
4
|
-
module ActiveRecord
|
5
|
-
|
6
|
-
# SchemaPlus modifies ActiveRecord's schema dumper to include foreign
|
7
|
-
# key constraints and views.
|
8
|
-
#
|
9
|
-
# Additionally, index and foreign key constraint definitions are dumped
|
10
|
-
# inline in the create_table block. (This is done for elegance, but
|
11
|
-
# also because Sqlite3 does not allow foreign key constraints to be
|
12
|
-
# added to a table after it has been defined.)
|
13
|
-
#
|
14
|
-
# The tables and views are dumped in alphabetical order, subject to
|
15
|
-
# topological sort constraints that a table must be dumped before any
|
16
|
-
# view that references it or table that has a foreign key constaint to
|
17
|
-
# it.
|
18
|
-
#
|
19
|
-
module SchemaDumper
|
20
|
-
|
21
|
-
include TSort
|
22
|
-
|
23
|
-
def self.included(base) #:nodoc:
|
24
|
-
base.class_eval do
|
25
|
-
private
|
26
|
-
alias_method_chain :table, :schema_plus
|
27
|
-
alias_method_chain :tables, :schema_plus
|
28
|
-
alias_method_chain :indexes, :schema_plus
|
29
|
-
alias_method_chain :foreign_keys, :schema_plus if private_method_defined? :foreign_keys
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def foreign_keys_with_schema_plus(*)
|
36
|
-
# do nothing. this overrides AR 4.2's foreign key dumping method, which isn't needed
|
37
|
-
# because we're dong them inline
|
38
|
-
end
|
39
|
-
|
40
|
-
def break_fk_cycles #:nodoc:
|
41
|
-
strongly_connected_components.select{|component| component.size > 1}.each do |tables|
|
42
|
-
table = tables.sort.last
|
43
|
-
backref_fks = @inline_fks[table].select{|fk| tables.include?(fk.references_table_name)}
|
44
|
-
@inline_fks[table] -= backref_fks
|
45
|
-
@dump_dependencies[table] -= backref_fks.collect(&:references_table_name)
|
46
|
-
backref_fks.each do |fk|
|
47
|
-
@backref_fks[fk.references_table_name] << fk
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def tables_with_schema_plus(stream) #:nodoc:
|
53
|
-
@table_dumps = {}
|
54
|
-
@inline_fks = Hash.new{ |h, k| h[k] = [] }
|
55
|
-
@backref_fks = Hash.new{ |h, k| h[k] = [] }
|
56
|
-
@dump_dependencies = {}
|
57
|
-
|
58
|
-
if @connection.respond_to?(:enums)
|
59
|
-
@connection.enums.each do |schema, name, values|
|
60
|
-
params = [name.inspect]
|
61
|
-
params << values.map(&:inspect).join(', ')
|
62
|
-
params << ":schema => #{schema.inspect}" if schema != 'public'
|
63
|
-
|
64
|
-
stream.puts " create_enum #{params.join(', ')}"
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
if "#{::ActiveRecord::VERSION::MAJOR}.#{::ActiveRecord::VERSION::MINOR}".to_r < "4.2".to_r
|
69
|
-
tables_without_schema_plus(nil)
|
70
|
-
else
|
71
|
-
tables_without_schema_plus(stream)
|
72
|
-
end
|
73
|
-
|
74
|
-
@connection.views.each do |view_name|
|
75
|
-
next if Array.wrap(::ActiveRecord::SchemaDumper.ignore_tables).any? {|pattern| view_name.match pattern}
|
76
|
-
definition = @connection.view_definition(view_name)
|
77
|
-
@table_dumps[view_name] = " create_view #{view_name.inspect}, #{definition.inspect}, :force => true\n"
|
78
|
-
end
|
79
|
-
|
80
|
-
re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(#{(@table_dumps.keys).join('|')})\b}
|
81
|
-
@table_dumps.keys.each do |table|
|
82
|
-
if @connection.views.include?(table)
|
83
|
-
dependencies = @connection.view_definition(table).scan(re_view_referent).flatten
|
84
|
-
else
|
85
|
-
@inline_fks[table] = @connection.foreign_keys(table)
|
86
|
-
dependencies = @inline_fks[table].collect(&:references_table_name)
|
87
|
-
end
|
88
|
-
# select against @table_dumps keys to respect filtering based on
|
89
|
-
# SchemaDumper.ignore_tables (which was taken into account
|
90
|
-
# increate @table_dumps)
|
91
|
-
@dump_dependencies[table] = dependencies.sort.uniq.select {|name| @table_dumps.has_key? name}
|
92
|
-
end
|
93
|
-
|
94
|
-
# Normally we dump foreign key constraints inline in the table
|
95
|
-
# definitions, both for visual cleanliness and because sqlite3
|
96
|
-
# doesn't allow foreign key constraints to be added afterwards.
|
97
|
-
# But in case there's a cycle in the constraint references, some
|
98
|
-
# constraints will need to be broken out then added later. (Adding
|
99
|
-
# constraints later won't work with sqlite3, but that means sqlite3
|
100
|
-
# won't let you create cycles in the first place.)
|
101
|
-
break_fk_cycles while strongly_connected_components.any?{|component| component.size > 1}
|
102
|
-
|
103
|
-
tsort().each do |table|
|
104
|
-
table_dump = @table_dumps[table]
|
105
|
-
if i = (table_dump =~ /^\s*[e]nd\s*$/)
|
106
|
-
table_dump.insert i, dump_indexes(table) + dump_foreign_keys(@inline_fks[table], :inline => true)
|
107
|
-
end
|
108
|
-
stream.print table_dump
|
109
|
-
stream.puts dump_foreign_keys(@backref_fks[table], :inline => false)+"\n" if @backref_fks[table].any?
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
def tsort_each_node(&block) #:nodoc:
|
115
|
-
@table_dumps.keys.sort.each(&block)
|
116
|
-
end
|
117
|
-
|
118
|
-
def tsort_each_child(table, &block) #:nodoc:
|
119
|
-
@dump_dependencies[table].each(&block)
|
120
|
-
end
|
121
|
-
|
122
|
-
def table_with_schema_plus(table, ignore) #:nodoc:
|
123
|
-
stream = StringIO.new
|
124
|
-
table_without_schema_plus(table, stream)
|
125
|
-
stream_string = stream.string
|
126
|
-
@connection.columns(table).each do |column|
|
127
|
-
if "#{::ActiveRecord::VERSION::MAJOR}.#{::ActiveRecord::VERSION::MINOR}".to_r < "4.2".to_r
|
128
|
-
if !column.default_expr.nil?
|
129
|
-
stream_string.gsub!("\"#{column.name}\"", "\"#{column.name}\", :default => { :expr => #{column.default_expr.inspect} }")
|
130
|
-
end
|
131
|
-
else
|
132
|
-
if !column.default_function.nil?
|
133
|
-
stream_string.gsub!("\"#{column.name}\"", "\"#{column.name}\", :default => { :expr => #{column.default_function.inspect} }")
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
@table_dumps[table] = stream_string
|
138
|
-
end
|
139
|
-
|
140
|
-
def indexes_with_schema_plus(table, stream) #:nodoc:
|
141
|
-
# do nothing. we've already taken care of indexes as part of
|
142
|
-
# dumping the tables
|
143
|
-
end
|
144
|
-
|
145
|
-
def dump_indexes(table) #:nodoc:
|
146
|
-
@connection.indexes(table).collect{ |index|
|
147
|
-
dump = " t.index"
|
148
|
-
dump << " #{index.columns.inspect}," unless index.columns.blank?
|
149
|
-
dump << " :name => #{index.name.inspect}"
|
150
|
-
dump << ", :unique => true" if index.unique
|
151
|
-
dump << ", :kind => \"#{index.kind}\"" unless index.kind.blank?
|
152
|
-
unless index.columns.blank?
|
153
|
-
dump << ", :case_sensitive => false" unless index.case_sensitive?
|
154
|
-
dump << ", :conditions => #{index.conditions.inspect}" unless index.conditions.blank?
|
155
|
-
index_lengths = index.lengths.compact if index.lengths.is_a?(Array)
|
156
|
-
dump << ", :length => #{Hash[*index.columns.zip(index.lengths).flatten].inspect}" if index_lengths.present?
|
157
|
-
dump << ", :order => {" + index.orders.map{|column, val| "#{column.inspect} => #{val.inspect}"}.join(", ") + "}" unless index.orders.blank?
|
158
|
-
dump << ", :operator_class => {" + index.operator_classes.map{|column, val| "#{column.inspect} => #{val.inspect}"}.join(", ") + "}" unless index.operator_classes.blank?
|
159
|
-
else
|
160
|
-
dump << ", :expression => #{index.expression.inspect}"
|
161
|
-
end
|
162
|
-
dump << "\n"
|
163
|
-
}.sort.join
|
164
|
-
end
|
165
|
-
|
166
|
-
def dump_foreign_keys(foreign_keys, opts={}) #:nodoc:
|
167
|
-
foreign_keys.collect{ |foreign_key| " " + foreign_key.to_dump(:inline => opts[:inline]) }.sort.join
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|
data/lib/schema_plus/railtie.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
module SchemaPlus
|
2
|
-
class Railtie < Rails::Railtie #:nodoc:
|
3
|
-
|
4
|
-
initializer 'schema_plus.insert', :before => "active_record.initialize_database" do
|
5
|
-
ActiveSupport.on_load(:active_record) do
|
6
|
-
SchemaPlus.insert
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
rake_tasks do
|
11
|
-
load 'rails/tasks/database.rake'
|
12
|
-
['db:schema:dump', 'db:schema:load'].each do |name|
|
13
|
-
if task = Rake.application.tasks.find { |task| task.name == name }
|
14
|
-
task.enhance(["schema_plus:load"])
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
20
|
-
end
|
@@ -1,211 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
-
|
3
|
-
|
4
|
-
describe "Index definition" do
|
5
|
-
|
6
|
-
let(:migration) { ::ActiveRecord::Migration }
|
7
|
-
|
8
|
-
before(:all) do
|
9
|
-
define_schema(:auto_create => false) 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
|
-
create_table :comments, :force => true do |t|
|
22
|
-
t.text :body
|
23
|
-
t.integer :post_id
|
24
|
-
t.foreign_key :post_id, :posts, :id
|
25
|
-
end
|
26
|
-
end
|
27
|
-
class User < ::ActiveRecord::Base ; end
|
28
|
-
class Post < ::ActiveRecord::Base ; end
|
29
|
-
class Comment < ::ActiveRecord::Base ; end
|
30
|
-
end
|
31
|
-
|
32
|
-
around(:each) do |example|
|
33
|
-
migration.suppress_messages do
|
34
|
-
example.run
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
after(:each) do
|
39
|
-
migration.remove_index :users, :name => 'users_login_index' if migration.index_name_exists? :users, 'users_login_index', true
|
40
|
-
end
|
41
|
-
|
42
|
-
context "when index is multicolumn" do
|
43
|
-
before(:each) do
|
44
|
-
migration.execute "CREATE INDEX users_login_index ON users (login, deleted_at)"
|
45
|
-
User.reset_column_information
|
46
|
-
@index = index_definition(%w[login deleted_at])
|
47
|
-
end
|
48
|
-
|
49
|
-
it "is included in User.indexes" do
|
50
|
-
expect(@index).not_to be_nil
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should correctly report supports_partial_indexes?" do
|
56
|
-
query = lambda { migration.execute "CREATE INDEX users_login_index ON users(login) WHERE deleted_at IS NULL" }
|
57
|
-
if migration.supports_partial_indexes?
|
58
|
-
expect(query).not_to raise_error
|
59
|
-
else
|
60
|
-
expect(query).to raise_error
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
it "should not crash on equality test with nil" do
|
65
|
-
index = ActiveRecord::ConnectionAdapters::IndexDefinition.new(:table, :column)
|
66
|
-
expect{index == nil}.to_not raise_error
|
67
|
-
expect(index == nil).to be false
|
68
|
-
end
|
69
|
-
|
70
|
-
|
71
|
-
context "when index is ordered", :mysql => :skip do
|
72
|
-
|
73
|
-
quotes = [
|
74
|
-
["unquoted", ''],
|
75
|
-
["double-quoted", '"'],
|
76
|
-
]
|
77
|
-
quotes += [
|
78
|
-
["single-quoted", "'"],
|
79
|
-
["back-quoted", '`']
|
80
|
-
] if SchemaDev::Rspec::Helpers.sqlite3?
|
81
|
-
|
82
|
-
quotes.each do |quotename, quote|
|
83
|
-
it "index definition includes orders for #{quotename} columns" do
|
84
|
-
migration.execute "CREATE INDEX users_login_index ON users (#{quote}login#{quote} DESC, #{quote}deleted_at#{quote} ASC)"
|
85
|
-
User.reset_column_information
|
86
|
-
index = index_definition(%w[login deleted_at])
|
87
|
-
expect(index.orders).to eq({"login" => :desc, "deleted_at" => :asc})
|
88
|
-
end
|
89
|
-
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
context "when case insensitive is added", :postgresql => :only do
|
95
|
-
|
96
|
-
before(:each) do
|
97
|
-
migration.execute "CREATE INDEX users_login_index ON users(LOWER(login))"
|
98
|
-
User.reset_column_information
|
99
|
-
@index = User.indexes.detect { |i| i.expression =~ /lower\(\(login\)::text\)/i }
|
100
|
-
end
|
101
|
-
|
102
|
-
it "is included in User.indexes" do
|
103
|
-
expect(@index).not_to be_nil
|
104
|
-
end
|
105
|
-
|
106
|
-
it "is not case_sensitive" do
|
107
|
-
expect(@index).not_to be_case_sensitive
|
108
|
-
end
|
109
|
-
|
110
|
-
it "its column should not be case sensitive" do
|
111
|
-
expect(User.columns.find{|column| column.name == "login"}).not_to be_case_sensitive
|
112
|
-
end
|
113
|
-
|
114
|
-
it "defines expression" do
|
115
|
-
expect(@index.expression).to eq("lower((login)::text)")
|
116
|
-
end
|
117
|
-
|
118
|
-
it "doesn't define conditions" do
|
119
|
-
expect(@index.conditions).to be_nil
|
120
|
-
end
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
context "when index is partial" do
|
126
|
-
before(:each) do
|
127
|
-
migration.execute "CREATE INDEX users_login_index ON users(login) WHERE deleted_at IS NULL"
|
128
|
-
User.reset_column_information
|
129
|
-
@index = index_definition("login")
|
130
|
-
end
|
131
|
-
|
132
|
-
it "is included in User.indexes" do
|
133
|
-
expect(User.indexes.select { |index| index.columns == ["login"] }.size).to eq(1)
|
134
|
-
end
|
135
|
-
|
136
|
-
it "is case_sensitive" do
|
137
|
-
expect(@index).to be_case_sensitive
|
138
|
-
end
|
139
|
-
|
140
|
-
it "doesn't define expression" do
|
141
|
-
expect(@index.expression).to be_nil
|
142
|
-
end
|
143
|
-
|
144
|
-
it "defines conditions" do
|
145
|
-
expect(@index.conditions).to match %r{[(]?deleted_at IS NULL[)]?}
|
146
|
-
end
|
147
|
-
|
148
|
-
end if ::ActiveRecord::Migration.supports_partial_indexes?
|
149
|
-
|
150
|
-
context "when index contains expression", :postgresql => :only do
|
151
|
-
before(:each) do
|
152
|
-
migration.execute "CREATE INDEX users_login_index ON users (extract(EPOCH from deleted_at)) WHERE deleted_at IS NULL"
|
153
|
-
User.reset_column_information
|
154
|
-
@index = User.indexes.detect { |i| i.expression.present? }
|
155
|
-
end
|
156
|
-
|
157
|
-
it "exists" do
|
158
|
-
expect(@index).not_to be_nil
|
159
|
-
end
|
160
|
-
|
161
|
-
it "doesnt have columns defined" do
|
162
|
-
expect(@index.columns).to be_empty
|
163
|
-
end
|
164
|
-
|
165
|
-
it "is case_sensitive" do
|
166
|
-
expect(@index).to be_case_sensitive
|
167
|
-
end
|
168
|
-
|
169
|
-
it "defines expression" do
|
170
|
-
expect(@index.expression).to eq("date_part('epoch'::text, deleted_at)")
|
171
|
-
end
|
172
|
-
|
173
|
-
it "defines conditions" do
|
174
|
-
expect(@index.conditions).to eq("(deleted_at IS NULL)")
|
175
|
-
end
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
context "when index has a non-btree type", :postgresql => :only do
|
180
|
-
before(:each) do
|
181
|
-
migration.execute "CREATE INDEX users_login_index ON users USING hash(login)"
|
182
|
-
User.reset_column_information
|
183
|
-
@index = User.indexes.detect { |i| i.name == "users_login_index" }
|
184
|
-
end
|
185
|
-
|
186
|
-
it "exists" do
|
187
|
-
expect(@index).not_to be_nil
|
188
|
-
end
|
189
|
-
|
190
|
-
it "defines kind" do
|
191
|
-
expect(@index.kind).to eq("hash")
|
192
|
-
end
|
193
|
-
|
194
|
-
it "does not define expression" do
|
195
|
-
expect(@index.expression).to be_nil
|
196
|
-
end
|
197
|
-
|
198
|
-
it "does not define order" do
|
199
|
-
expect(@index.orders).to be_blank
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
protected
|
206
|
-
def index_definition(column_names)
|
207
|
-
User.indexes.detect { |index| index.columns == Array(column_names) }
|
208
|
-
end
|
209
|
-
|
210
|
-
|
211
|
-
end
|
data/spec/index_spec.rb
DELETED
@@ -1,249 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/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(:auto_create => false) 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
|
-
create_table :comments, :force => true do |t|
|
27
|
-
t.text :body
|
28
|
-
t.integer :post_id
|
29
|
-
t.foreign_key :post_id, :posts, :id
|
30
|
-
end
|
31
|
-
end
|
32
|
-
class User < ::ActiveRecord::Base ; end
|
33
|
-
class Post < ::ActiveRecord::Base ; end
|
34
|
-
class Comment < ::ActiveRecord::Base ; end
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
after(:each) do
|
39
|
-
migration.suppress_messages do
|
40
|
-
migration.remove_index(:users, :name => @index.name) if (@index ||= nil)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should create index when called without additional options" do
|
45
|
-
add_index(:users, :login)
|
46
|
-
expect(index_for(:login)).not_to be_nil
|
47
|
-
end
|
48
|
-
|
49
|
-
it "should create unique index" do
|
50
|
-
add_index(:users, :login, :unique => true)
|
51
|
-
expect(index_for(:login).unique).to eq(true)
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should assign given name" do
|
55
|
-
add_index(:users, :login, :name => 'users_login_index')
|
56
|
-
expect(index_for(:login).name).to eq('users_login_index')
|
57
|
-
end
|
58
|
-
|
59
|
-
it "should assign order", :mysql => :skip do
|
60
|
-
add_index(:users, [:login, :deleted_at], :order => {:login => :desc, :deleted_at => :asc})
|
61
|
-
expect(index_for([:login, :deleted_at]).orders).to eq({"login" => :desc, "deleted_at" => :asc})
|
62
|
-
end
|
63
|
-
|
64
|
-
it "should respect algorithm: :concurrently", :postgresql => :only do
|
65
|
-
expect(connection).to receive(:execute).with(/CREATE INDEX CONCURRENTLY/)
|
66
|
-
add_index(:users, :login, :algorithm => :concurrently)
|
67
|
-
end if ActiveRecord::VERSION::MAJOR > 3
|
68
|
-
|
69
|
-
context "for duplicate index" do
|
70
|
-
it "should not complain if the index is the same" do
|
71
|
-
add_index(:users, :login)
|
72
|
-
expect(index_for(:login)).not_to be_nil
|
73
|
-
expect(ActiveRecord::Base.logger).to receive(:warn).with(/login.*Skipping/)
|
74
|
-
expect { add_index(:users, :login) }.to_not raise_error
|
75
|
-
expect(index_for(:login)).not_to be_nil
|
76
|
-
end
|
77
|
-
it "should complain if the index is different" do
|
78
|
-
add_index(:users, :login, :unique => true)
|
79
|
-
expect(index_for(:login)).not_to be_nil
|
80
|
-
expect { add_index(:users, :login) }.to raise_error
|
81
|
-
expect(index_for(:login)).not_to be_nil
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
context "extra features", :postgresql => :only do
|
86
|
-
|
87
|
-
it "should assign conditions" do
|
88
|
-
add_index(:users, :login, :conditions => 'deleted_at IS NULL')
|
89
|
-
expect(index_for(:login).conditions).to eq('(deleted_at IS NULL)')
|
90
|
-
end
|
91
|
-
|
92
|
-
it "should assign expression, conditions and kind" do
|
93
|
-
add_index(:users, :expression => "USING hash (upper(login)) WHERE deleted_at IS NULL", :name => 'users_login_index')
|
94
|
-
@index = User.indexes.detect { |i| i.expression.present? }
|
95
|
-
expect(@index.expression).to eq("upper((login)::text)")
|
96
|
-
expect(@index.conditions).to eq("(deleted_at IS NULL)")
|
97
|
-
expect(@index.kind).to eq("hash")
|
98
|
-
end
|
99
|
-
|
100
|
-
it "should allow to specify expression, conditions and kind separately" do
|
101
|
-
add_index(:users, :kind => "hash", :expression => "upper(login)", :conditions => "deleted_at IS NULL", :name => 'users_login_index')
|
102
|
-
@index = User.indexes.detect { |i| i.expression.present? }
|
103
|
-
expect(@index.expression).to eq("upper((login)::text)")
|
104
|
-
expect(@index.conditions).to eq("(deleted_at IS NULL)")
|
105
|
-
expect(@index.kind).to eq("hash")
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should allow to specify kind" do
|
109
|
-
add_index(:users, :login, :kind => "hash")
|
110
|
-
expect(index_for(:login).kind).to eq('hash')
|
111
|
-
end
|
112
|
-
|
113
|
-
it "should assign operator_class" do
|
114
|
-
add_index(:users, :login, :operator_class => 'varchar_pattern_ops')
|
115
|
-
expect(index_for(:login).operator_classes).to eq({"login" => 'varchar_pattern_ops'})
|
116
|
-
end
|
117
|
-
|
118
|
-
it "should assign multiple operator_classes" do
|
119
|
-
add_index(:users, [:login, :address], :operator_class => {:login => 'varchar_pattern_ops', :address => 'text_pattern_ops'})
|
120
|
-
expect(index_for([:login, :address]).operator_classes).to eq({"login" => 'varchar_pattern_ops', "address" => 'text_pattern_ops'})
|
121
|
-
end
|
122
|
-
|
123
|
-
it "should allow to specify actual expression only" do
|
124
|
-
add_index(:users, :expression => "upper(login)", :name => 'users_login_index')
|
125
|
-
@index = User.indexes.detect { |i| i.name == 'users_login_index' }
|
126
|
-
expect(@index.expression).to eq("upper((login)::text)")
|
127
|
-
end
|
128
|
-
|
129
|
-
it "should raise if no column given and expression is missing" do
|
130
|
-
expect { add_index(:users, :name => 'users_login_index') }.to raise_error(ArgumentError, /expression/)
|
131
|
-
end
|
132
|
-
|
133
|
-
it "should raise if expression without name is given" do
|
134
|
-
expect { add_index(:users, :expression => "USING btree (login)") }.to raise_error(ArgumentError, /name/)
|
135
|
-
end
|
136
|
-
|
137
|
-
it "should raise if expression is given and case_sensitive is false" do
|
138
|
-
expect { add_index(:users, :name => 'users_login_index', :expression => "USING btree (login)", :case_sensitive => false) }.to raise_error(ArgumentError, /use LOWER/i)
|
139
|
-
end
|
140
|
-
|
141
|
-
end
|
142
|
-
|
143
|
-
protected
|
144
|
-
|
145
|
-
def index_for(column_names)
|
146
|
-
@index = User.indexes.detect { |i| i.columns == Array(column_names).collect(&:to_s) }
|
147
|
-
end
|
148
|
-
|
149
|
-
end
|
150
|
-
|
151
|
-
describe "remove_index" do
|
152
|
-
|
153
|
-
before(:each) do
|
154
|
-
connection.tables.each do |table| connection.drop_table table, cascade: true end
|
155
|
-
define_schema(:auto_create => false) do
|
156
|
-
create_table :users, :force => true do |t|
|
157
|
-
t.string :login
|
158
|
-
t.datetime :deleted_at
|
159
|
-
end
|
160
|
-
end
|
161
|
-
class User < ::ActiveRecord::Base ; end
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
it "removes index by column name (symbols)" do
|
166
|
-
add_index :users, :login
|
167
|
-
expect(User.indexes.length).to eq(1)
|
168
|
-
remove_index :users, :login
|
169
|
-
expect(User.indexes.length).to eq(0)
|
170
|
-
end
|
171
|
-
|
172
|
-
it "removes index by column name (symbols)" do
|
173
|
-
add_index :users, :login
|
174
|
-
expect(User.indexes.length).to eq(1)
|
175
|
-
remove_index 'users', 'login'
|
176
|
-
expect(User.indexes.length).to eq(0)
|
177
|
-
end
|
178
|
-
|
179
|
-
it "removes multi-column index by column names (symbols)" do
|
180
|
-
add_index :users, [:login, :deleted_at]
|
181
|
-
expect(User.indexes.length).to eq(1)
|
182
|
-
remove_index :users, [:login, :deleted_at]
|
183
|
-
expect(User.indexes.length).to eq(0)
|
184
|
-
end
|
185
|
-
|
186
|
-
it "removes multi-column index by column names (strings)" do
|
187
|
-
add_index 'users', [:login, :deleted_at]
|
188
|
-
expect(User.indexes.length).to eq(1)
|
189
|
-
remove_index 'users', ['login', 'deleted_at']
|
190
|
-
expect(User.indexes.length).to eq(0)
|
191
|
-
end
|
192
|
-
|
193
|
-
it "removes index using column option" do
|
194
|
-
add_index :users, :login
|
195
|
-
expect(User.indexes.length).to eq(1)
|
196
|
-
remove_index :users, column: :login
|
197
|
-
expect(User.indexes.length).to eq(0)
|
198
|
-
end
|
199
|
-
|
200
|
-
it "removes index if_exists" do
|
201
|
-
add_index :users, :login
|
202
|
-
expect(User.indexes.length).to eq(1)
|
203
|
-
remove_index :users, :login, :if_exists => true
|
204
|
-
expect(User.indexes.length).to eq(0)
|
205
|
-
end
|
206
|
-
|
207
|
-
it "removes multi-column index if exists" do
|
208
|
-
add_index :users, [:login, :deleted_at]
|
209
|
-
expect(User.indexes.length).to eq(1)
|
210
|
-
remove_index :users, [:login, :deleted_at], :if_exists => true
|
211
|
-
expect(User.indexes.length).to eq(0)
|
212
|
-
end
|
213
|
-
|
214
|
-
it "removes index if_exists using column option" do
|
215
|
-
add_index :users, :login
|
216
|
-
expect(User.indexes.length).to eq(1)
|
217
|
-
remove_index :users, column: :login, :if_exists => true
|
218
|
-
expect(User.indexes.length).to eq(0)
|
219
|
-
end
|
220
|
-
|
221
|
-
it "raises exception if doesn't exist" do
|
222
|
-
expect {
|
223
|
-
remove_index :users, :login
|
224
|
-
}.to raise_error
|
225
|
-
end
|
226
|
-
|
227
|
-
it "doesn't raise exception with :if_exists" do
|
228
|
-
expect {
|
229
|
-
remove_index :users, :login, :if_exists => true
|
230
|
-
}.to_not raise_error
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
protected
|
235
|
-
def add_index(*args)
|
236
|
-
migration.suppress_messages do
|
237
|
-
migration.add_index(*args)
|
238
|
-
end
|
239
|
-
User.reset_column_information
|
240
|
-
end
|
241
|
-
|
242
|
-
def remove_index(*args)
|
243
|
-
migration.suppress_messages do
|
244
|
-
migration.remove_index(*args)
|
245
|
-
end
|
246
|
-
User.reset_column_information
|
247
|
-
end
|
248
|
-
|
249
|
-
end
|