· 8 years ago · May 29, 2018, 08:54 PM
1From 3111fc9b26e64370b2216e7690d6542de8206db7 Mon Sep 17 00:00:00 2001
2From: Andrew White <andyw@pixeltrix.co.uk>
3Date: Thu, 18 Mar 2010 16:49:23 +0000
4Subject: [PATCH] Add column and index query methods to ActiveRecord::Schema
5
6---
7 .../abstract/schema_definitions.rb | 10 ++
8 .../abstract/schema_statements.rb | 57 ++++++++++-
9 .../test/cases/active_schema_test_mysql.rb | 6 +-
10 activerecord/test/cases/migration_test.rb | 108 +++++++++++++++++++-
11 4 files changed, 170 insertions(+), 11 deletions(-)
12
13diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
14index 7d58bc2..7691b6a 100644
15--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
16+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
17@@ -582,6 +582,11 @@ module ActiveRecord
18 @base.add_column(@table_name, column_name, type, options)
19 end
20
21+ # Checks to see if a column exists. See SchemaStatements#column_exists?
22+ def column_exists?(column_name, type = nil, options = nil)
23+ @base.column_exists?(@table_name, column_name, type, options)
24+ end
25+
26 # Adds a new index to the table. +column_name+ can be a single Symbol, or
27 # an Array of Symbols. See SchemaStatements#add_index
28 #
29@@ -596,6 +601,11 @@ module ActiveRecord
30 @base.add_index(@table_name, column_name, options)
31 end
32
33+ # Checks to see if an index exists. See SchemaStatements#index_exists?
34+ def index_exists?(column_name, options = {})
35+ @base.index_exists?(@table_name, column_name, options)
36+ end
37+
38 # Adds timestamps (created_at and updated_at) columns to the table. See SchemaStatements#add_timestamps
39 # ===== Example
40 # t.timestamps
41diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
42index 638e5d7..5625dba 100644
43--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
44+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
45@@ -24,10 +24,59 @@ module ActiveRecord
46 # Returns an array of indexes for the given table.
47 # def indexes(table_name, name = nil) end
48
49+ # Checks to see if an index exists on a table for a given index definition
50+ #
51+ # === Examples
52+ # # Check an index exists
53+ # index_exists?(:suppliers, :company_id)
54+ #
55+ # # Check an index on multiple columns exists
56+ # index_exists?(:suppliers, [:company_id, :company_type])
57+ #
58+ # # Check a unique index exists
59+ # index_exists?(:suppliers, :company_id, :unique => true)
60+ #
61+ # # Check an index with a custom name exists
62+ # index_exists?(:suppliers, :company_id, :name => "idx_company_id"
63+ def index_exists?(table_name, column_name, options = {})
64+ column_names = Array.wrap(column_name)
65+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
66+ if options[:unique]
67+ indexes(table_name).any?{ |i| i.unique && i.name == index_name }
68+ else
69+ indexes(table_name).any?{ |i| i.name == index_name }
70+ end
71+ end
72+
73 # Returns an array of Column objects for the table specified by +table_name+.
74 # See the concrete implementation for details on the expected parameter values.
75 def columns(table_name, name = nil) end
76
77+ # Checks to see if a column exists in a given table.
78+ #
79+ # === Examples
80+ # # Check a column exists
81+ # column_exists?(:suppliers, :name)
82+ #
83+ # # Check a column exists of a particular type
84+ # column_exists?(:suppliers, :name, :string)
85+ #
86+ # # Check a column exists with a specific definition
87+ # column_exists?(:suppliers, :name, :string, :limit => 100)
88+ def column_exists?(table_name, column_name, type = nil, options = nil)
89+ column_name = column_name.to_s
90+ if type
91+ if options
92+ sql_type = type_to_sql(type, options[:limit], options[:precision], options[:scale])
93+ columns(table_name).any?{ |c| c.name == column_name && c.sql_type == sql_type }
94+ else
95+ columns(table_name).any?{ |c| c.name == column_name && c.type == type }
96+ end
97+ else
98+ columns(table_name).any?{ |c| c.name == column_name }
99+ end
100+ end
101+
102 # Creates a new table with the name +table_name+. +table_name+ may either
103 # be a String or a Symbol.
104 #
105@@ -293,7 +342,7 @@ module ActiveRecord
106 @logger.warn("Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters. Skipping.")
107 return
108 end
109- if index_exists?(table_name, index_name, false)
110+ if index_name_exists?(table_name, index_name, false)
111 @logger.warn("Index name '#{index_name}' on table '#{table_name}' already exists. Skipping.")
112 return
113 end
114@@ -314,7 +363,7 @@ module ActiveRecord
115 # remove_index :accounts, :name => :by_branch_party
116 def remove_index(table_name, options = {})
117 index_name = index_name(table_name, options)
118- unless index_exists?(table_name, index_name, true)
119+ unless index_name_exists?(table_name, index_name, true)
120 @logger.warn("Index name '#{index_name}' on table '#{table_name}' does not exist. Skipping.")
121 return
122 end
123@@ -351,11 +400,11 @@ module ActiveRecord
124 end
125 end
126
127- # Verify the existence of an index.
128+ # Verify the existence of an index with a given name.
129 #
130 # The default argument is returned if the underlying implementation does not define the indexes method,
131 # as there's no way to determine the correct answer in that case.
132- def index_exists?(table_name, index_name, default)
133+ def index_name_exists?(table_name, index_name, default)
134 return default unless respond_to?(:indexes)
135 indexes(table_name).detect { |i| i.name == index_name }
136 end
137diff --git a/activerecord/test/cases/active_schema_test_mysql.rb b/activerecord/test/cases/active_schema_test_mysql.rb
138index d7431e5..6e66455 100644
139--- a/activerecord/test/cases/active_schema_test_mysql.rb
140+++ b/activerecord/test/cases/active_schema_test_mysql.rb
141@@ -17,8 +17,8 @@ class ActiveSchemaTest < ActiveRecord::TestCase
142 end
143
144 def test_add_index
145- # add_index calls index_exists? which can't work since execute is stubbed
146- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_exists?) do |*|
147+ # add_index calls index_name_exists? which can't work since execute is stubbed
148+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:define_method, :index_name_exists?) do |*|
149 false
150 end
151 expected = "CREATE INDEX `index_people_on_last_name` ON `people` (`last_name`)"
152@@ -35,7 +35,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase
153
154 expected = "CREATE INDEX `index_people_on_last_name_and_first_name` ON `people` (`last_name`(15), `first_name`(10))"
155 assert_equal expected, add_index(:people, [:last_name, :first_name], :length => {:last_name => 15, :first_name => 10})
156- ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_exists?)
157+ ActiveRecord::ConnectionAdapters::MysqlAdapter.send(:remove_method, :index_name_exists?)
158 end
159
160 def test_drop_table
161diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
162index 9ece2fb..99a3a12 100644
163--- a/activerecord/test/cases/migration_test.rb
164+++ b/activerecord/test/cases/migration_test.rb
165@@ -128,9 +128,9 @@ if ActiveRecord::Base.connection.supports_migrations?
166 good_index_name = 'x' * Person.connection.index_name_length
167 too_long_index_name = good_index_name + 'x'
168 assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => too_long_index_name) }
169- assert !Person.connection.index_exists?("people", too_long_index_name, false)
170+ assert !Person.connection.index_name_exists?("people", too_long_index_name, false)
171 assert_nothing_raised { Person.connection.add_index("people", "first_name", :name => good_index_name) }
172- assert Person.connection.index_exists?("people", good_index_name, false)
173+ assert Person.connection.index_name_exists?("people", good_index_name, false)
174 end
175
176 def test_remove_nonexistent_index
177@@ -146,8 +146,8 @@ if ActiveRecord::Base.connection.supports_migrations?
178 Person.connection.add_index('people', [:first_name], :name => 'old_idx')
179 assert_nothing_raised { Person.connection.rename_index('people', 'old_idx', 'new_idx') }
180 # if the adapter doesn't support the indexes call, pick defaults that let the test pass
181- assert !Person.connection.index_exists?('people', 'old_idx', false)
182- assert Person.connection.index_exists?('people', 'new_idx', true)
183+ assert !Person.connection.index_name_exists?('people', 'old_idx', false)
184+ assert Person.connection.index_name_exists?('people', 'new_idx', true)
185 end
186 end
187
188@@ -158,6 +158,53 @@ if ActiveRecord::Base.connection.supports_migrations?
189 end
190 end
191
192+ def test_index_exists
193+ Person.connection.create_table :testings do |t|
194+ t.column :foo, :string, :limit => 100
195+ t.column :bar, :string, :limit => 100
196+ end
197+ Person.connection.add_index :testings, :foo
198+
199+ assert Person.connection.index_exists?(:testings, :foo)
200+ assert !Person.connection.index_exists?(:testings, :bar)
201+ ensure
202+ Person.connection.drop_table :testings rescue nil
203+ end
204+
205+ def test_index_exists_on_multiple_columns
206+ Person.connection.create_table :testings do |t|
207+ t.column :foo, :string, :limit => 100
208+ t.column :bar, :string, :limit => 100
209+ end
210+ Person.connection.add_index :testings, [:foo, :bar]
211+
212+ assert Person.connection.index_exists?(:testings, [:foo, :bar])
213+ ensure
214+ Person.connection.drop_table :testings rescue nil
215+ end
216+
217+ def test_unique_index_exists
218+ Person.connection.create_table :testings do |t|
219+ t.column :foo, :string, :limit => 100
220+ end
221+ Person.connection.add_index :testings, :foo, :unique => true
222+
223+ assert Person.connection.index_exists?(:testings, :foo, :unique => true)
224+ ensure
225+ Person.connection.drop_table :testings rescue nil
226+ end
227+
228+ def test_named_index_exists
229+ Person.connection.create_table :testings do |t|
230+ t.column :foo, :string, :limit => 100
231+ end
232+ Person.connection.add_index :testings, :foo, :name => "custom_index_name"
233+
234+ assert Person.connection.index_exists?(:testings, :foo, :name => "custom_index_name")
235+ ensure
236+ Person.connection.drop_table :testings rescue nil
237+ end
238+
239 def testing_table_with_only_foo_attribute
240 Person.connection.create_table :testings, :id => false do |t|
241 t.column :foo, :string
242@@ -974,6 +1021,45 @@ if ActiveRecord::Base.connection.supports_migrations?
243 assert_nil Person.new.first_name
244 end
245
246+ def test_column_exists
247+ Person.connection.create_table :testings do |t|
248+ t.column :foo, :string
249+ end
250+
251+ assert Person.connection.column_exists?(:testings, :foo)
252+ assert !Person.connection.column_exists?(:testings, :bar)
253+ ensure
254+ Person.connection.drop_table :testings rescue nil
255+ end
256+
257+ def test_column_exists_with_type
258+ Person.connection.create_table :testings do |t|
259+ t.column :foo, :string
260+ t.column :bar, :decimal, :precision => 8, :scale => 2
261+ end
262+
263+ assert Person.connection.column_exists?(:testings, :foo, :string)
264+ assert !Person.connection.column_exists?(:testings, :foo, :integer)
265+ assert Person.connection.column_exists?(:testings, :bar, :decimal)
266+ assert !Person.connection.column_exists?(:testings, :bar, :integer)
267+ ensure
268+ Person.connection.drop_table :testings rescue nil
269+ end
270+
271+ def test_column_exists_with_definition
272+ Person.connection.create_table :testings do |t|
273+ t.column :foo, :string, :limit => 100
274+ t.column :bar, :decimal, :precision => 8, :scale => 2
275+ end
276+
277+ assert Person.connection.column_exists?(:testings, :foo, :string, :limit => 100)
278+ assert !Person.connection.column_exists?(:testings, :foo, :string, :limit => 50)
279+ assert Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 8, :scale => 2)
280+ assert !Person.connection.column_exists?(:testings, :bar, :decimal, :precision => 10, :scale => 2)
281+ ensure
282+ Person.connection.drop_table :testings rescue nil
283+ end
284+
285 def test_add_table
286 assert !Reminder.table_exists?
287
288@@ -1684,6 +1770,20 @@ if ActiveRecord::Base.connection.supports_migrations?
289 end
290 end
291
292+ def test_index_exists
293+ with_change_table do |t|
294+ @connection.expects(:index_exists?).with(:delete_me, :bar, {})
295+ t.index_exists?(:bar)
296+ end
297+ end
298+
299+ def test_index_exists_with_options
300+ with_change_table do |t|
301+ @connection.expects(:index_exists?).with(:delete_me, :bar, {:unique => true})
302+ t.index_exists?(:bar, :unique => true)
303+ end
304+ end
305+
306 def test_change_changes_column
307 with_change_table do |t|
308 @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
309--
3101.7.1