schema_plus 1.8.9 → 2.0.0.pre1
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 +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
|