pg_saurus 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.markdown +341 -0
  3. data/lib/colorized_text.rb +33 -0
  4. data/lib/core_ext/active_record/connection_adapters/abstract/schema_statements.rb +155 -0
  5. data/lib/core_ext/active_record/connection_adapters/postgresql_adapter.rb +191 -0
  6. data/lib/core_ext/active_record/errors.rb +6 -0
  7. data/lib/core_ext/active_record/schema_dumper.rb +42 -0
  8. data/lib/pg_saurus/connection_adapters/abstract_adapter/comment_methods.rb +80 -0
  9. data/lib/pg_saurus/connection_adapters/abstract_adapter/foreigner_methods.rb +67 -0
  10. data/lib/pg_saurus/connection_adapters/abstract_adapter/index_methods.rb +6 -0
  11. data/lib/pg_saurus/connection_adapters/abstract_adapter/schema_methods.rb +20 -0
  12. data/lib/pg_saurus/connection_adapters/abstract_adapter.rb +20 -0
  13. data/lib/pg_saurus/connection_adapters/foreign_key_definition.rb +5 -0
  14. data/lib/pg_saurus/connection_adapters/index_definition.rb +8 -0
  15. data/lib/pg_saurus/connection_adapters/postgresql_adapter/comment_methods.rb +114 -0
  16. data/lib/pg_saurus/connection_adapters/postgresql_adapter/extension_methods.rb +124 -0
  17. data/lib/pg_saurus/connection_adapters/postgresql_adapter/foreigner_methods.rb +221 -0
  18. data/lib/pg_saurus/connection_adapters/postgresql_adapter/index_methods.rb +42 -0
  19. data/lib/pg_saurus/connection_adapters/postgresql_adapter/schema_methods.rb +58 -0
  20. data/lib/pg_saurus/connection_adapters/postgresql_adapter/translate_exception.rb +20 -0
  21. data/lib/pg_saurus/connection_adapters/postgresql_adapter/view_methods.rb +17 -0
  22. data/lib/pg_saurus/connection_adapters/postgresql_adapter.rb +28 -0
  23. data/lib/pg_saurus/connection_adapters/table/comment_methods.rb +58 -0
  24. data/lib/pg_saurus/connection_adapters/table/foreigner_methods.rb +51 -0
  25. data/lib/pg_saurus/connection_adapters/table.rb +17 -0
  26. data/lib/pg_saurus/connection_adapters.rb +9 -0
  27. data/lib/pg_saurus/create_index_concurrently.rb +227 -0
  28. data/lib/pg_saurus/engine.rb +57 -0
  29. data/lib/pg_saurus/errors.rb +6 -0
  30. data/lib/pg_saurus/migration/command_recorder/comment_methods.rb +68 -0
  31. data/lib/pg_saurus/migration/command_recorder/extension_methods.rb +25 -0
  32. data/lib/pg_saurus/migration/command_recorder/foreigner_methods.rb +31 -0
  33. data/lib/pg_saurus/migration/command_recorder/schema_methods.rb +59 -0
  34. data/lib/pg_saurus/migration/command_recorder/view_methods.rb +31 -0
  35. data/lib/pg_saurus/migration/command_recorder.rb +17 -0
  36. data/lib/pg_saurus/migration.rb +4 -0
  37. data/lib/pg_saurus/schema_dumper/comment_methods.rb +51 -0
  38. data/lib/pg_saurus/schema_dumper/extension_methods.rb +29 -0
  39. data/lib/pg_saurus/schema_dumper/foreigner_methods.rb +63 -0
  40. data/lib/pg_saurus/schema_dumper/schema_methods.rb +27 -0
  41. data/lib/pg_saurus/schema_dumper/view_methods.rb +32 -0
  42. data/lib/pg_saurus/schema_dumper.rb +28 -0
  43. data/lib/pg_saurus/tools.rb +104 -0
  44. data/lib/pg_saurus/version.rb +4 -0
  45. data/lib/pg_saurus.rb +18 -0
  46. data/lib/tasks/pg_saurus_tasks.rake +4 -0
  47. metadata +226 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a10802e45cc1cb9e289081e96b420ddbcf4459c1
4
+ data.tar.gz: 5f570fb003f9551eb793213c99586a5b6cf8597d
5
+ SHA512:
6
+ metadata.gz: 22b9bcda65cdbec23a53f07f41fd6a37b808f605e3ba65a972f6ba0c19b17f966e9092107b807208ece28ff318fbe3082c6a5cc78ba69ac9e69fad80c4eb5e28
7
+ data.tar.gz: 8c57bb5f82920f46ee793236cf031dfa1b2e758a1bc97f24c683ec42c483d661e3bab117bb888c210619a8036e31741751447419e35109cc093f02c2d3e0bd55
data/README.markdown ADDED
@@ -0,0 +1,341 @@
1
+ # PgSaurus
2
+
3
+ [![Build Status](https://secure.travis-ci.org/HornsAndHooves/pg_saurus.png)](http://travis-ci.org/HornsAndHooves/pg_saurus)
4
+ [![Dependency Status](https://gemnasium.com/HornsAndHooves/pg_saurus.png)](https://gemnasium.com/HornsAndHooves/pg_saurus)
5
+ [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/HornsAndHooves/pg_saurus)
6
+
7
+ ActiveRecord extension to get more from PostgreSQL:
8
+
9
+ * Create/drop schemas.
10
+ * Set/remove comments on columns and tables.
11
+ * Use foreign keys.
12
+ * Use partial indexes.
13
+ * Run index creation concurrently.
14
+
15
+ PgSaurus is a fork of PgPower.
16
+
17
+ ## Environment notes
18
+
19
+ PgSaurus was tested with Rails 4.0.x, Ruby 2.1.2.
20
+
21
+ NOTE: JRuby is not yet supported. The current ActiveRecord JDBC
22
+ adapter has its own Rails4-compatible method named "create_schema" which
23
+ conflicts with this gem.
24
+
25
+ ## Schemas
26
+
27
+ ### Create schema
28
+
29
+ In migrations you can use `create_schema` and `drop_schema` methods like this:
30
+
31
+ ```ruby
32
+ class ReplaceDemographySchemaWithPolitics < ActiveRecord::Migration
33
+ def change
34
+ drop_schema 'demography'
35
+ create_schema 'politics'
36
+
37
+ drop_schema_if_exists('demography')
38
+ create_schema_if_not_exists('politics')
39
+ end
40
+ end
41
+ ```
42
+ ### Create table
43
+
44
+ Use schema `:schema` option to specify schema name:
45
+
46
+ ```ruby
47
+ create_table "countries", :schema => "demography" do |t|
48
+ # columns goes here
49
+ end
50
+ ```
51
+ ### Move table to another schema
52
+
53
+ Move table `countries` from `demography` schema to `public`:
54
+
55
+ ```ruby
56
+ move_table_to_schema 'demography.countries', :public
57
+ ```
58
+ ## Table and column comments
59
+
60
+ Provides the following methods to manage comments:
61
+
62
+ * `set_table_comment(table_name, comment)`
63
+ * `remove_table_comment(table_name)`
64
+ * `set_column_comment(table_name, column_name, comment)`
65
+ * `remove_column_comment(table_name, column_name, comment)`
66
+ * `set_column_comments(table_name, comments)`
67
+ * `remove_column_comments(table_name, *comments)`
68
+
69
+
70
+ ### Examples
71
+
72
+ Set a comment on the given table.
73
+
74
+ ```ruby
75
+ set_table_comment :phone_numbers, 'This table stores phone numbers that conform to the North American Numbering Plan.'
76
+ ```
77
+ Sets a comment on a given column of a given table.
78
+
79
+ ```ruby
80
+ set_column_comment :phone_numbers, :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
81
+ ```
82
+ Removes any comment from the given table.
83
+
84
+ ```ruby
85
+ remove_table_comment :phone_numbers
86
+ ```
87
+ Removes any comment from the given column of a given table.
88
+
89
+ ```ruby
90
+ remove_column_comment :phone_numbers, :npa
91
+ ```
92
+ Set comments on multiple columns in the table.
93
+
94
+ ```ruby
95
+ set_column_comments :phone_numbers, :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
96
+ :nxx => 'Central Office Number'
97
+ ```
98
+ Remove comments from multiple columns in the table.
99
+
100
+ ```ruby
101
+ remove_column_comments :phone_numbers, :npa, :nxx
102
+ ```
103
+ PgSaurus also adds extra methods to change_table.
104
+
105
+ Set comments:
106
+
107
+ ```ruby
108
+ change_table :phone_numbers do |t|
109
+ t.set_table_comment 'This table stores phone numbers that conform to the North American Numbering Plan.'
110
+ t.set_column_comment :npa, 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.'
111
+ end
112
+
113
+ change_table :phone_numbers do |t|
114
+ t.set_column_comments :npa => 'Numbering Plan Area Code - Allowed ranges: [2-9] for first digit, [0-9] for second and third digit.',
115
+ :nxx => 'Central Office Number'
116
+ end
117
+ ```
118
+ Remove comments:
119
+
120
+ ```ruby
121
+ change_table :phone_numbers do |t|
122
+ t.remove_table_comment
123
+ t.remove_column_comment :npa
124
+ end
125
+
126
+ change_table :phone_numbers do |t|
127
+ t.remove_column_comments :npa, :nxx
128
+ end
129
+ ```
130
+ ## Foreign keys
131
+
132
+ PgPower imported some code of [foreigner](https://github.com/matthuhiggins/foreigner)
133
+ gem and patched it to be schema-aware. Support was also added for index auto-generation.
134
+
135
+ You should disable `foreigner` in your Gemfile if you want to use `pg_saurus`.
136
+
137
+ If you do not want to generate an index, pass the :exclude_index => true option.
138
+
139
+ The syntax is compatible with `foreigner`:
140
+
141
+
142
+ Add foreign key from `comments` to `posts` using `post_id` column as key by default:
143
+
144
+ ```ruby
145
+ add_foreign_key(:comments, :posts)
146
+ ```
147
+ Specify key explicitly:
148
+
149
+ ```ruby
150
+ add_foreign_key(:comments, :posts, :column => :blog_post_id)
151
+ ```
152
+ Specify name of foreign key constraint:
153
+
154
+ ```ruby
155
+ add_foreign_key(:comments, :posts, :name => "comments_posts_fk")
156
+ ```
157
+ It works with schemas as expected:
158
+
159
+ ```ruby
160
+ add_foreign_key('blog.comments', 'blog.posts')
161
+ ```
162
+ Adds the index 'index_comments_on_post_id':
163
+
164
+ ```ruby
165
+ add_foreign_key(:comments, :posts)
166
+ ```
167
+ Does not add an index:
168
+
169
+ ```ruby
170
+ add_foreign_key(:comments, :posts, :exclude_index => true)
171
+ ```
172
+ ## Partial Indexes
173
+
174
+ Rails 4.x [pull request](https://github.com/rails/rails/pull/4956) was used as a
175
+ starting point to patch it to be schema-aware.
176
+
177
+ ### Examples
178
+
179
+ Add a partial index to a table
180
+
181
+ ```ruby
182
+ add_index(:comments, [:country_id, :user_id], :where => 'active')
183
+ ```
184
+ Add a partial index to a schema table
185
+
186
+ ```ruby
187
+ add_index('blog.comments', :user_id, :where => 'active')
188
+ ```
189
+ ## Indexes on Expressions
190
+
191
+ PostgreSQL supports indexes on expressions. Right now, only basic functional
192
+ expressions are supported.
193
+
194
+ ### Examples
195
+
196
+ Add an index to a column with a function
197
+
198
+ ```ruby
199
+ add_index(:comments, "lower(text)")
200
+ ```
201
+
202
+ You can also specify index access method
203
+
204
+ ```ruby
205
+ create_extension 'btree_gist'
206
+ create_extension 'fuzzystrmatch'
207
+ add_index(:comments, 'dmetaphone(author)', :using => 'gist')
208
+ ```
209
+
210
+ ## Concurrent index creation
211
+
212
+ PostgreSQL supports concurent index creation. We added that feature to migration
213
+ DSL on index and foreign keys creation.
214
+
215
+ ### Examples
216
+
217
+ Add an index concurrently to a table
218
+
219
+ ```ruby
220
+ add_index :table, :column_id, :concurrently => true
221
+ ```
222
+
223
+ Add an index concurrently along with foreign key
224
+
225
+ ```ruby
226
+ add_foreign_key :table1, :table2, :column => :column_id, :concurrent_index => true
227
+ ```
228
+
229
+ ## Loading/Unloading postgresql extension modules
230
+
231
+ Postgresql is shipped with a number of [extension modules](http://www.postgresql.org/docs/9.1/static/contrib.html).
232
+ PgSaurus provides some tools
233
+ to [load](http://www.postgresql.org/docs/9.1/static/sql-createextension.html)/[unload](http://www.postgresql.org/docs/9.1/static/sql-dropextension.html)
234
+ such modules by the means of migrations.
235
+
236
+ Please note. CREATE/DROP EXTENSION command has been introduced in postgresql 9.1 only.
237
+ So this functionality will not be available for the previous versions.
238
+
239
+ ### Examples
240
+
241
+ Load [fuzzystrmatch](http://www.postgresql.org/docs/9.1/static/fuzzystrmatch.html) extension module
242
+ and create its objects in schema *public*:
243
+
244
+ ```ruby
245
+ create_extension "fuzzystrmatch"
246
+ ```
247
+
248
+
249
+ Load version *1.0* of the [btree_gist](http://www.postgresql.org/docs/9.1/static/btree-gist.html) extension module
250
+ and create its objects in schema *demography*.
251
+
252
+ ```ruby
253
+ create_extension "btree_gist", :schema_name => "demography", :version => "1.0"
254
+ ```
255
+
256
+ Unload extension module:
257
+
258
+ ```ruby
259
+ drop_extension "fuzzystrmatch"
260
+ ```
261
+
262
+ ## Views
263
+
264
+ Version 1.6.0 introduces experimental support for creating views. This API should only be used
265
+ with the understanding that it is preliminary 'alpha' at best.
266
+
267
+ ### Example
268
+
269
+ ```ruby
270
+ create_view "demography.citizens_view", "select * from demography.citizens"
271
+ ```
272
+
273
+ ## Tools
274
+
275
+ PgSaurus::Tools provides number of useful methods:
276
+
277
+ ```ruby
278
+ PgSaurus::Tools.create_schema "services" # => create new PG schema "services"
279
+ PgSaurus::Tools.create_schema "nets" # => create new PG schema "nets"
280
+ PgSaurus::Tools.drop_schema "services" # => remove the PG schema "services"
281
+ PgSaurus::Tools.create_schema_if_not_exists "nets" # => Does nothing -- schema "nets" already exists
282
+ PgSaurus::Tools.drop_schema_if_exists "services" # => Does nothing -- schema "services" doesn't exist
283
+ PgSaurus::Tools.schemas # => ["public", "information_schema", "nets"]
284
+ PgSaurus::Tools.index_exists?(table, columns, options) # => returns true if an index exists for the given params
285
+ ```
286
+
287
+ ## Rails 3
288
+
289
+ PgSaurus does not support Rails 3.
290
+
291
+ ## Running tests:
292
+
293
+ * Ensure your postgresql has postgres-contrib (Ubuntu) package installed. Tests depend on btree_gist and fuzzystrmatch extensions
294
+ * If on Mac, see below for installing contrib packages
295
+ * Configure `spec/dummy/config/database.yml` for development and test environments.
296
+ * Run `rake spec`.
297
+ * Make sure migrations don't raise exceptions and all specs pass.
298
+
299
+ ### Installing contrib packages on Mac OS X:
300
+ * This assumes you are using MacPorts to install Postgres. If using homebrew or the Postgres App, you will need to adjust the instructions accordingly (please add to this README when you do)
301
+ * Assuming you installed with default options (including auto-clean), you will need to rebuild the postgresql port and keep the build files
302
+ * `sudo port -k -s build postgresql91`
303
+ * (adjust the version number above appropriately)
304
+ * Now you can make and install the btree_gist and any other contrib modules
305
+ * `cd ```port work postgresql91```/postgresql-9.1.7/contrib/btree_gist`
306
+ * (again, you may need to adjust the version number to your specific version)
307
+ * `sudo make all`
308
+ * `sudo make install`
309
+ * Done!
310
+
311
+ ## TODO:
312
+
313
+ Support for JRuby:
314
+
315
+ * Jdbc driver provides its own `create_schema(schema, user)` method - solve conflicts.
316
+
317
+ ## Credits
318
+
319
+ * [Potapov Sergey](https://github.com/greyblake) - schema support
320
+ * [Arthur Shagall](https://github.com/albertosaurus) - thanks for [pg_comment](https://github.com/albertosaurus/pg_comment)
321
+ * [Matthew Higgins](https://github.com/matthuhiggins) - thanks for [foreigner](https://github.com/matthuhiggins/foreigner), which was used as a base for the foreign key support
322
+ * [Artem Ignatyev](https://github.com/cryo28) - extension modules load/unload support
323
+ * [Marcelo Silveira](https://github.com/mhfs) - thanks for rails partial index support that was backported into this gem
324
+
325
+ ## Copyright and License
326
+
327
+ * Copyright (c) 2014 HornsAndHooves.
328
+ * Initial foreign key code taken from foreigner, Copyright (c) 2009 Matthew Higgins
329
+ * pg_comment Copyright (c) 2011 Arthur Shagall
330
+ * Partial index Copyright (c) 2012 Marcelo Silveira
331
+ * PgPower Copyright (c) 2012 TMX Credit.
332
+
333
+ Released under the MIT License. See the MIT-LICENSE file for more details.
334
+
335
+ ## Contributing
336
+
337
+ Contributions are welcome. However, before issuing a pull request, please make sure of the following:
338
+
339
+ * All specs are passing (under ruby 1.9.3+)
340
+ * Any new features have test coverage.
341
+ * Anything that breaks backward compatibility has a very good reason for doing so.
@@ -0,0 +1,33 @@
1
+ # Colorizes text with ASCII colors.
2
+ # == Usage:
3
+ # include ColorizedText
4
+ #
5
+ # puts green "OK" # => green output
6
+ # puts bold "Running... # => bold output
7
+ # puts bold green "OK!!!" # => bold green output
8
+ module ColorizedText
9
+ # Colorize text using ASCII color code
10
+ def colorize(text, code)
11
+ "\033[#{code}m#{text}\033[0m"
12
+ end
13
+
14
+ # :nodoc:
15
+ def yellow(text)
16
+ colorize(text, 33)
17
+ end
18
+
19
+ # :nodoc:
20
+ def green(text)
21
+ colorize(text, 32)
22
+ end
23
+
24
+ # :nodoc:
25
+ def red(text)
26
+ colorize(text, 31)
27
+ end
28
+
29
+ # :nodoc:
30
+ def bold(text)
31
+ colorize(text, 1)
32
+ end
33
+ end
@@ -0,0 +1,155 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters # :nodoc:
3
+ module SchemaStatements # :nodoc:
4
+ # Regexp used to find the function name and function argument of a
5
+ # function call
6
+ FUNCTIONAL_INDEX_REGEXP = /(\w+)\(((?:'.+'(?:::\w+)?, *)*)(\w+)\)/
7
+
8
+
9
+ # Redefine original add_index method to handle :concurrently option.
10
+ #
11
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
12
+ # an Array of Symbols.
13
+ #
14
+ # ====== Creating a partial index
15
+ # add_index(:accounts, [:branch_id, :party_id], :using => 'BTree'
16
+ # :unique => true, :concurrently => true, :where => 'active')
17
+ # generates
18
+ # CREATE UNIQUE INDEX CONCURRENTLY
19
+ # index_accounts_on_branch_id_and_party_id
20
+ # ON
21
+ # accounts(branch_id, party_id)
22
+ # WHERE
23
+ # active
24
+ #
25
+ def add_index_with_concurrently(table_name, column_name, options = {})
26
+ creation_method = options.delete(:concurrently) ? 'CONCURRENTLY' : nil
27
+
28
+ index_name,
29
+ index_type,
30
+ index_columns,
31
+ index_options,
32
+ index_algorithm,
33
+ index_using = add_index_options(table_name, column_name, options)
34
+
35
+ # GOTCHA:
36
+ # It ensures that there is no existing index only for the case when the index
37
+ # is created concurrently to avoid changing the error behavior for default
38
+ # index creation.
39
+ # -- zekefast 2012-09-25
40
+ # GOTCHA:
41
+ # This check prevents invalid index creation, so after migration failed
42
+ # here there is no need to go to database and clean it from invalid
43
+ # indexes. But note that this handles only one of the cases when index
44
+ # creation can fail!!! All other case should be procesed manually.
45
+ # -- zekefast 2012-09-25
46
+ if creation_method.present? && index_exists?(table_name, column_name, options)
47
+ raise ::PgSaurus::IndexExistsError,
48
+ "Index #{index_name} for `#{table_name}.#{column_name}` " \
49
+ "column can not be created concurrently, because such index already exists."
50
+ end
51
+
52
+ statements = []
53
+ statements << "CREATE #{index_type} INDEX"
54
+ statements << creation_method if creation_method.present?
55
+ statements << index_algorithm if index_algorithm.present?
56
+ statements << quote_column_name(index_name)
57
+ statements << "ON"
58
+ statements << quote_table_name(table_name)
59
+ statements << index_using if index_using.present?
60
+ statements << "(#{index_columns})" if index_columns.present?
61
+ statements << index_options if index_options.present?
62
+
63
+ sql = statements.join(' ')
64
+ execute(sql)
65
+ end
66
+
67
+ # Check to see if an index exists on a table for a given index definition.
68
+ #
69
+ # === Examples
70
+ # # Check that a partial index exists
71
+ # index_exists?(:suppliers, :company_id, :where => 'active')
72
+ #
73
+ # # GIVEN: 'index_suppliers_on_company_id' UNIQUE, btree (company_id) WHERE active
74
+ # index_exists?(:suppliers, :company_id, :unique => true, :where => 'active') => true
75
+ # index_exists?(:suppliers, :company_id, :unique => true) => false
76
+ #
77
+ def index_exists?(table_name, column_name, options = {})
78
+ column_names = Array.wrap(column_name)
79
+ index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names)
80
+
81
+ # Always compare the index name
82
+ default_comparator = lambda { |index| index.name == index_name }
83
+ comparators = [default_comparator]
84
+
85
+ # Add a comparator for each index option that is part of the query
86
+ index_options = [:unique, :where]
87
+ index_options.each do |index_option|
88
+ comparators << if options.key?(index_option)
89
+ lambda do |index|
90
+ pg_where_clause = index.send(index_option)
91
+ # pg does nothing to boolean clauses, e.g. 'where active' => 'where active'
92
+ if pg_where_clause.is_a?(TrueClass) or pg_where_clause.is_a?(FalseClass)
93
+ pg_where_clause == options[index_option]
94
+ else
95
+ # pg adds parentheses around non-boolean clauses, e.g. 'where color IS NULL' => 'where (color is NULL)'
96
+ pg_where_clause.gsub!(/[()]/,'')
97
+ # pg casts string comparison ::text. e.g. "where color = 'black'" => "where ((color)::text = 'black'::text)"
98
+ pg_where_clause.gsub!(/::text/,'')
99
+ # prevent case from impacting the comparison
100
+ pg_where_clause.downcase == options[index_option].downcase
101
+ end
102
+ end
103
+ else
104
+ # If the given index_option is not an argument to the index_exists? query,
105
+ # select only those pg indexes that do not have the component
106
+ lambda { |index| index.send(index_option).blank? }
107
+ end
108
+ end
109
+
110
+ # Search all indexes for any that match all comparators
111
+ indexes(table_name).any? do |index|
112
+ comparators.inject(true) { |ret, comparator| ret && comparator.call(index) }
113
+ end
114
+ end
115
+
116
+ # Derive the name of the index from the given table name and options hash.
117
+ def index_name(table_name, options) #:nodoc:
118
+ if Hash === options # legacy support
119
+ if options[:column]
120
+ column_names = Array.wrap(options[:column]).map {|c| expression_index_name(c)}
121
+ "index_#{table_name}_on_#{column_names * '_and_'}"
122
+ elsif options[:name]
123
+ options[:name]
124
+ else
125
+ raise ArgumentError, "You must specify the index name"
126
+ end
127
+ else
128
+ index_name(table_name, :column => options)
129
+ end
130
+ end
131
+
132
+ # Override super method to provide support for expression column names.
133
+ def quoted_columns_for_index(column_names, options = {})
134
+ column_names.map do |name|
135
+ if name =~ FUNCTIONAL_INDEX_REGEXP
136
+ "#{$1}(#{$2}#{quote_column_name($3)})"
137
+ else
138
+ quote_column_name(name)
139
+ end
140
+ end
141
+ end
142
+ protected :quoted_columns_for_index
143
+
144
+ # Map an expression to a name appropriate for an index.
145
+ def expression_index_name(column_name)
146
+ if column_name =~ FUNCTIONAL_INDEX_REGEXP
147
+ "#{$1.downcase}_#{$3}"
148
+ else
149
+ column_name
150
+ end
151
+ end
152
+ private :expression_index_name
153
+ end
154
+ end
155
+ end