activerecord 6.0.3.7 → 6.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (245) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +770 -741
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/association_relation.rb +22 -14
  8. data/lib/active_record/associations.rb +114 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +40 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +52 -48
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +4 -4
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +27 -7
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +32 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +186 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +112 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +36 -69
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +129 -88
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  91. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  92. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  95. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  96. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  97. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  98. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  99. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  100. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  102. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  104. data/lib/active_record/connection_handling.rb +210 -71
  105. data/lib/active_record/core.rb +220 -55
  106. data/lib/active_record/database_configurations.rb +124 -85
  107. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  108. data/lib/active_record/database_configurations/database_config.rb +52 -9
  109. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  110. data/lib/active_record/database_configurations/url_config.rb +15 -40
  111. data/lib/active_record/delegated_type.rb +209 -0
  112. data/lib/active_record/destroy_association_async_job.rb +36 -0
  113. data/lib/active_record/enum.rb +27 -10
  114. data/lib/active_record/errors.rb +47 -12
  115. data/lib/active_record/explain.rb +9 -4
  116. data/lib/active_record/explain_subscriber.rb +1 -1
  117. data/lib/active_record/fixture_set/file.rb +10 -17
  118. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  119. data/lib/active_record/fixture_set/render_context.rb +1 -1
  120. data/lib/active_record/fixture_set/table_row.rb +2 -2
  121. data/lib/active_record/fixtures.rb +54 -8
  122. data/lib/active_record/gem_version.rb +3 -3
  123. data/lib/active_record/inheritance.rb +40 -18
  124. data/lib/active_record/insert_all.rb +33 -6
  125. data/lib/active_record/integration.rb +3 -5
  126. data/lib/active_record/internal_metadata.rb +15 -4
  127. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  128. data/lib/active_record/locking/optimistic.rb +22 -16
  129. data/lib/active_record/locking/pessimistic.rb +6 -2
  130. data/lib/active_record/log_subscriber.rb +26 -8
  131. data/lib/active_record/middleware/database_selector.rb +4 -1
  132. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  133. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  134. data/lib/active_record/migration.rb +113 -83
  135. data/lib/active_record/migration/command_recorder.rb +47 -27
  136. data/lib/active_record/migration/compatibility.rb +67 -17
  137. data/lib/active_record/model_schema.rb +88 -13
  138. data/lib/active_record/nested_attributes.rb +2 -3
  139. data/lib/active_record/no_touching.rb +1 -1
  140. data/lib/active_record/persistence.rb +50 -45
  141. data/lib/active_record/query_cache.rb +15 -5
  142. data/lib/active_record/querying.rb +11 -6
  143. data/lib/active_record/railtie.rb +64 -44
  144. data/lib/active_record/railties/databases.rake +253 -98
  145. data/lib/active_record/readonly_attributes.rb +4 -0
  146. data/lib/active_record/reflection.rb +70 -57
  147. data/lib/active_record/relation.rb +96 -67
  148. data/lib/active_record/relation/batches.rb +38 -31
  149. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  150. data/lib/active_record/relation/calculations.rb +101 -44
  151. data/lib/active_record/relation/delegation.rb +2 -1
  152. data/lib/active_record/relation/finder_methods.rb +45 -15
  153. data/lib/active_record/relation/from_clause.rb +1 -1
  154. data/lib/active_record/relation/merger.rb +27 -25
  155. data/lib/active_record/relation/predicate_builder.rb +57 -33
  156. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  157. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
  158. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  159. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  160. data/lib/active_record/relation/query_methods.rb +330 -195
  161. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  162. data/lib/active_record/relation/spawn_methods.rb +6 -5
  163. data/lib/active_record/relation/where_clause.rb +104 -57
  164. data/lib/active_record/result.rb +41 -33
  165. data/lib/active_record/runtime_registry.rb +2 -2
  166. data/lib/active_record/sanitization.rb +6 -17
  167. data/lib/active_record/schema_dumper.rb +34 -4
  168. data/lib/active_record/schema_migration.rb +0 -4
  169. data/lib/active_record/scoping/named.rb +6 -17
  170. data/lib/active_record/secure_token.rb +16 -8
  171. data/lib/active_record/serialization.rb +5 -3
  172. data/lib/active_record/signed_id.rb +116 -0
  173. data/lib/active_record/statement_cache.rb +20 -4
  174. data/lib/active_record/store.rb +2 -2
  175. data/lib/active_record/suppressor.rb +2 -2
  176. data/lib/active_record/table_metadata.rb +36 -52
  177. data/lib/active_record/tasks/database_tasks.rb +139 -113
  178. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  179. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  180. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  181. data/lib/active_record/test_databases.rb +5 -4
  182. data/lib/active_record/test_fixtures.rb +37 -16
  183. data/lib/active_record/timestamp.rb +4 -6
  184. data/lib/active_record/touch_later.rb +21 -21
  185. data/lib/active_record/transactions.rb +15 -64
  186. data/lib/active_record/type.rb +8 -1
  187. data/lib/active_record/type/serialized.rb +6 -2
  188. data/lib/active_record/type/time.rb +10 -0
  189. data/lib/active_record/type_caster/connection.rb +0 -1
  190. data/lib/active_record/type_caster/map.rb +8 -5
  191. data/lib/active_record/validations.rb +1 -0
  192. data/lib/active_record/validations/numericality.rb +35 -0
  193. data/lib/active_record/validations/uniqueness.rb +24 -4
  194. data/lib/arel.rb +5 -13
  195. data/lib/arel/attributes/attribute.rb +4 -0
  196. data/lib/arel/collectors/bind.rb +5 -0
  197. data/lib/arel/collectors/composite.rb +8 -0
  198. data/lib/arel/collectors/sql_string.rb +7 -0
  199. data/lib/arel/collectors/substitute_binds.rb +7 -0
  200. data/lib/arel/nodes.rb +3 -1
  201. data/lib/arel/nodes/binary.rb +82 -8
  202. data/lib/arel/nodes/bind_param.rb +8 -0
  203. data/lib/arel/nodes/casted.rb +21 -9
  204. data/lib/arel/nodes/equality.rb +6 -9
  205. data/lib/arel/nodes/grouping.rb +3 -0
  206. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  207. data/lib/arel/nodes/in.rb +8 -1
  208. data/lib/arel/nodes/infix_operation.rb +13 -1
  209. data/lib/arel/nodes/join_source.rb +1 -1
  210. data/lib/arel/nodes/node.rb +7 -6
  211. data/lib/arel/nodes/ordering.rb +27 -0
  212. data/lib/arel/nodes/sql_literal.rb +3 -0
  213. data/lib/arel/nodes/table_alias.rb +7 -3
  214. data/lib/arel/nodes/unary.rb +0 -1
  215. data/lib/arel/predications.rb +12 -18
  216. data/lib/arel/select_manager.rb +1 -2
  217. data/lib/arel/table.rb +13 -5
  218. data/lib/arel/visitors.rb +0 -7
  219. data/lib/arel/visitors/dot.rb +14 -2
  220. data/lib/arel/visitors/mysql.rb +11 -1
  221. data/lib/arel/visitors/postgresql.rb +15 -4
  222. data/lib/arel/visitors/to_sql.rb +89 -78
  223. data/lib/rails/generators/active_record/migration.rb +6 -1
  224. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  225. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  226. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  227. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  228. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  229. metadata +30 -31
  230. data/lib/active_record/advisory_lock_base.rb +0 -18
  231. data/lib/active_record/attribute_decorators.rb +0 -88
  232. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  233. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  234. data/lib/active_record/define_callbacks.rb +0 -22
  235. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  236. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  237. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  238. data/lib/arel/attributes.rb +0 -22
  239. data/lib/arel/visitors/depth_first.rb +0 -203
  240. data/lib/arel/visitors/ibm_db.rb +0 -34
  241. data/lib/arel/visitors/informix.rb +0 -62
  242. data/lib/arel/visitors/mssql.rb +0 -156
  243. data/lib/arel/visitors/oracle.rb +0 -158
  244. data/lib/arel/visitors/oracle12.rb +0 -65
  245. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -33,6 +33,14 @@ module ActiveRecord
33
33
  @comment = comment
34
34
  end
35
35
 
36
+ def column_options
37
+ {
38
+ length: lengths,
39
+ order: orders,
40
+ opclass: opclasses,
41
+ }
42
+ end
43
+
36
44
  private
37
45
  def concise_options(options)
38
46
  if columns.size == options.size && options.values.uniq.size == 1
@@ -69,6 +77,8 @@ module ActiveRecord
69
77
 
70
78
  ChangeColumnDefinition = Struct.new(:column, :name) #:nodoc:
71
79
 
80
+ CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc:
81
+
72
82
  PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
73
83
 
74
84
  ForeignKeyDefinition = Struct.new(:from_table, :to_table, :options) do #:nodoc:
@@ -105,8 +115,9 @@ module ActiveRecord
105
115
  !ActiveRecord::SchemaDumper.fk_ignore_pattern.match?(name) if name
106
116
  end
107
117
 
108
- def defined_for?(to_table: nil, **options)
118
+ def defined_for?(to_table: nil, validate: nil, **options)
109
119
  (to_table.nil? || to_table.to_s == self.to_table) &&
120
+ (validate.nil? || validate == options.fetch(:validate, validate)) &&
110
121
  options.all? { |k, v| self.options[k].to_s == v.to_s }
111
122
  end
112
123
 
@@ -116,6 +127,21 @@ module ActiveRecord
116
127
  end
117
128
  end
118
129
 
130
+ CheckConstraintDefinition = Struct.new(:table_name, :expression, :options) do
131
+ def name
132
+ options[:name]
133
+ end
134
+
135
+ def validate?
136
+ options.fetch(:validate, true)
137
+ end
138
+ alias validated? validate?
139
+
140
+ def export_name_on_schema_dump?
141
+ !ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
142
+ end
143
+ end
144
+
119
145
  class ReferenceDefinition # :nodoc:
120
146
  def initialize(
121
147
  name,
@@ -138,13 +164,12 @@ module ActiveRecord
138
164
  end
139
165
 
140
166
  def add_to(table)
141
- columns.each do |column_options|
142
- kwargs = column_options.extract_options!
143
- table.column(*column_options, **kwargs)
167
+ columns.each do |name, type, options|
168
+ table.column(name, type, **options)
144
169
  end
145
170
 
146
171
  if index
147
- table.index(column_names, index_options)
172
+ table.index(column_names, **index_options(table.name))
148
173
  end
149
174
 
150
175
  if foreign_key
@@ -163,8 +188,14 @@ module ActiveRecord
163
188
  as_options(polymorphic).merge(options.slice(:null, :first, :after))
164
189
  end
165
190
 
166
- def index_options
167
- as_options(index)
191
+ def polymorphic_index_name(table_name)
192
+ "index_#{table_name}_on_#{name}"
193
+ end
194
+
195
+ def index_options(table_name)
196
+ index_options = as_options(index)
197
+ index_options[:name] ||= polymorphic_index_name(table_name) if polymorphic
198
+ index_options
168
199
  end
169
200
 
170
201
  def foreign_key_options
@@ -222,7 +253,7 @@ module ActiveRecord
222
253
  end
223
254
 
224
255
  class_methods do
225
- private def define_column_methods(*column_types) # :nodoc:
256
+ def define_column_methods(*column_types) # :nodoc:
226
257
  column_types.each do |column_type|
227
258
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
228
259
  def #{column_type}(*names, **options)
@@ -232,6 +263,7 @@ module ActiveRecord
232
263
  RUBY
233
264
  end
234
265
  end
266
+ private :define_column_methods
235
267
  end
236
268
  end
237
269
 
@@ -241,7 +273,7 @@ module ActiveRecord
241
273
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
242
274
  # is actually of this type:
243
275
  #
244
- # class SomeMigration < ActiveRecord::Migration[5.0]
276
+ # class SomeMigration < ActiveRecord::Migration[6.0]
245
277
  # def up
246
278
  # create_table :foo do |t|
247
279
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -256,7 +288,7 @@ module ActiveRecord
256
288
  class TableDefinition
257
289
  include ColumnMethods
258
290
 
259
- attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys
291
+ attr_reader :name, :temporary, :if_not_exists, :options, :as, :comment, :indexes, :foreign_keys, :check_constraints
260
292
 
261
293
  def initialize(
262
294
  conn,
@@ -273,6 +305,7 @@ module ActiveRecord
273
305
  @indexes = []
274
306
  @foreign_keys = []
275
307
  @primary_keys = nil
308
+ @check_constraints = []
276
309
  @temporary = temporary
277
310
  @if_not_exists = if_not_exists
278
311
  @options = options
@@ -361,7 +394,7 @@ module ActiveRecord
361
394
  # t.references :tagger, polymorphic: true
362
395
  # t.references :taggable, polymorphic: { default: 'Photo' }, index: false
363
396
  # end
364
- def column(name, type, **options)
397
+ def column(name, type, index: nil, **options)
365
398
  name = name.to_s
366
399
  type = type.to_sym if type
367
400
 
@@ -373,9 +406,13 @@ module ActiveRecord
373
406
  end
374
407
  end
375
408
 
376
- index_options = options.delete(:index)
377
- index(name, index_options.is_a?(Hash) ? index_options : {}) if index_options
378
409
  @columns_hash[name] = new_column_definition(name, type, **options)
410
+
411
+ if index
412
+ index_options = index.is_a?(Hash) ? index : {}
413
+ index(name, **index_options)
414
+ end
415
+
379
416
  self
380
417
  end
381
418
 
@@ -389,7 +426,7 @@ module ActiveRecord
389
426
  # This is primarily used to track indexes that need to be created after the table
390
427
  #
391
428
  # index(:account_id, name: 'index_projects_on_account_id')
392
- def index(column_name, options = {})
429
+ def index(column_name, **options)
393
430
  indexes << [column_name, options]
394
431
  end
395
432
 
@@ -397,6 +434,10 @@ module ActiveRecord
397
434
  foreign_keys << [table_name, options]
398
435
  end
399
436
 
437
+ def check_constraint(expression, **options)
438
+ check_constraints << [expression, options]
439
+ end
440
+
400
441
  # Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
401
442
  # <tt>:updated_at</tt> to the table. See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
402
443
  #
@@ -456,14 +497,16 @@ module ActiveRecord
456
497
 
457
498
  class AlterTable # :nodoc:
458
499
  attr_reader :adds
459
- attr_reader :foreign_key_adds
460
- attr_reader :foreign_key_drops
500
+ attr_reader :foreign_key_adds, :foreign_key_drops
501
+ attr_reader :check_constraint_adds, :check_constraint_drops
461
502
 
462
503
  def initialize(td)
463
504
  @td = td
464
505
  @adds = []
465
506
  @foreign_key_adds = []
466
507
  @foreign_key_drops = []
508
+ @check_constraint_adds = []
509
+ @check_constraint_drops = []
467
510
  end
468
511
 
469
512
  def name; @td.name; end
@@ -476,6 +519,14 @@ module ActiveRecord
476
519
  @foreign_key_drops << name
477
520
  end
478
521
 
522
+ def add_check_constraint(expression, options)
523
+ @check_constraint_adds << CheckConstraintDefinition.new(name, expression, options)
524
+ end
525
+
526
+ def drop_check_constraint(constraint_name)
527
+ @check_constraint_drops << constraint_name
528
+ end
529
+
479
530
  def add_column(name, type, **options)
480
531
  name = name.to_s
481
532
  type = type.to_sym
@@ -496,9 +547,11 @@ module ActiveRecord
496
547
  # t.timestamps
497
548
  # t.change
498
549
  # t.change_default
550
+ # t.change_null
499
551
  # t.rename
500
552
  # t.references
501
553
  # t.belongs_to
554
+ # t.check_constraint
502
555
  # t.string
503
556
  # t.text
504
557
  # t.integer
@@ -520,6 +573,7 @@ module ActiveRecord
520
573
  # t.remove_references
521
574
  # t.remove_belongs_to
522
575
  # t.remove_index
576
+ # t.remove_check_constraint
523
577
  # t.remove_timestamps
524
578
  # end
525
579
  #
@@ -538,10 +592,12 @@ module ActiveRecord
538
592
  # t.column(:name, :string)
539
593
  #
540
594
  # See TableDefinition#column for details of the options you can use.
541
- def column(column_name, type, **options)
542
- index_options = options.delete(:index)
595
+ def column(column_name, type, index: nil, **options)
543
596
  @base.add_column(name, column_name, type, **options)
544
- index(column_name, index_options.is_a?(Hash) ? index_options : {}) if index_options
597
+ if index
598
+ index_options = index.is_a?(Hash) ? index : {}
599
+ index(column_name, **index_options)
600
+ end
545
601
  end
546
602
 
547
603
  # Checks to see if a column exists.
@@ -561,8 +617,8 @@ module ActiveRecord
561
617
  # t.index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
562
618
  #
563
619
  # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
564
- def index(column_name, options = {})
565
- @base.add_index(name, column_name, options)
620
+ def index(column_name, **options)
621
+ @base.add_index(name, column_name, **options)
566
622
  end
567
623
 
568
624
  # Checks to see if an index exists.
@@ -600,8 +656,8 @@ module ActiveRecord
600
656
  # t.change(:description, :text)
601
657
  #
602
658
  # See TableDefinition#column for details of the options you can use.
603
- def change(column_name, type, options = {})
604
- @base.change_column(name, column_name, type, options)
659
+ def change(column_name, type, **options)
660
+ @base.change_column(name, column_name, type, **options)
605
661
  end
606
662
 
607
663
  # Sets a new default value for a column.
@@ -615,14 +671,24 @@ module ActiveRecord
615
671
  @base.change_column_default(name, column_name, default_or_changes)
616
672
  end
617
673
 
674
+ # Sets or removes a NOT NULL constraint on a column.
675
+ #
676
+ # t.change_null(:qualification, true)
677
+ # t.change_null(:qualification, false, 0)
678
+ #
679
+ # See {connection.change_column_null}[rdoc-ref:SchemaStatements#change_column_null]
680
+ def change_null(column_name, null, default = nil)
681
+ @base.change_column_null(name, column_name, null, default)
682
+ end
683
+
618
684
  # Removes the column(s) from the table definition.
619
685
  #
620
686
  # t.remove(:qualification)
621
687
  # t.remove(:qualification, :experience)
622
688
  #
623
689
  # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
624
- def remove(*column_names)
625
- @base.remove_columns(name, *column_names)
690
+ def remove(*column_names, **options)
691
+ @base.remove_columns(name, *column_names, **options)
626
692
  end
627
693
 
628
694
  # Removes the given index from the table.
@@ -630,10 +696,11 @@ module ActiveRecord
630
696
  # t.remove_index(:branch_id)
631
697
  # t.remove_index(column: [:branch_id, :party_id])
632
698
  # t.remove_index(name: :by_branch_party)
699
+ # t.remove_index(:branch_id, name: :by_branch_party)
633
700
  #
634
701
  # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
635
- def remove_index(options = {})
636
- @base.remove_index(name, options)
702
+ def remove_index(column_name = nil, **options)
703
+ @base.remove_index(name, column_name, **options)
637
704
  end
638
705
 
639
706
  # Removes the timestamp columns (+created_at+ and +updated_at+) from the table.
@@ -708,6 +775,24 @@ module ActiveRecord
708
775
  def foreign_key_exists?(*args, **options)
709
776
  @base.foreign_key_exists?(name, *args, **options)
710
777
  end
778
+
779
+ # Adds a check constraint.
780
+ #
781
+ # t.check_constraint("price > 0", name: "price_check")
782
+ #
783
+ # See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint]
784
+ def check_constraint(*args)
785
+ @base.add_check_constraint(name, *args)
786
+ end
787
+
788
+ # Removes the given check constraint from the table.
789
+ #
790
+ # t.remove_check_constraint(name: "price_check")
791
+ #
792
+ # See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint]
793
+ def remove_check_constraint(*args)
794
+ @base.remove_check_constraint(name, *args)
795
+ end
711
796
  end
712
797
  end
713
798
  end
@@ -13,9 +13,9 @@ module ActiveRecord
13
13
  end
14
14
 
15
15
  def column_spec_for_primary_key(column)
16
- return {} if default_primary_key?(column)
17
- spec = { id: schema_type(column).inspect }
18
- spec.merge!(prepare_column_options(column).except!(:null, :comment))
16
+ spec = {}
17
+ spec[:id] = schema_type(column).inspect unless default_primary_key?(column)
18
+ spec.merge!(prepare_column_options(column).except!(:null))
19
19
  spec[:default] ||= "nil" if explicit_primary_key_default?(column)
20
20
  spec
21
21
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_record/migration/join_table"
4
3
  require "active_support/core_ext/string/access"
5
- require "active_support/deprecation"
6
4
  require "digest/sha2"
7
5
 
8
6
  module ActiveRecord
@@ -98,10 +96,14 @@ module ActiveRecord
98
96
  # # Check an index with a custom name exists
99
97
  # index_exists?(:suppliers, :company_id, name: "idx_company_id")
100
98
  #
101
- def index_exists?(table_name, column_name, options = {})
102
- column_names = Array(column_name).map(&:to_s)
99
+ def index_exists?(table_name, column_name, **options)
103
100
  checks = []
104
- checks << lambda { |i| Array(i.columns) == column_names }
101
+
102
+ if column_name.present?
103
+ column_names = Array(column_name).map(&:to_s)
104
+ checks << lambda { |i| Array(i.columns) == column_names }
105
+ end
106
+
105
107
  checks << lambda { |i| i.unique } if options[:unique]
106
108
  checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
107
109
 
@@ -291,37 +293,42 @@ module ActiveRecord
291
293
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
292
294
  #
293
295
  # See also TableDefinition#column for details on how to create columns.
294
- def create_table(table_name, **options)
295
- td = create_table_definition(table_name, **options)
296
+ def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
297
+ td = create_table_definition(table_name, **extract_table_options!(options))
296
298
 
297
- if options[:id] != false && !options[:as]
298
- pk = options.fetch(:primary_key) do
299
- Base.get_primary_key table_name.to_s.singularize
299
+ if id && !td.as
300
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
301
+
302
+ if id.is_a?(Hash)
303
+ options.merge!(id.except(:type))
304
+ id = id.fetch(:type, :primary_key)
300
305
  end
301
306
 
302
307
  if pk.is_a?(Array)
303
308
  td.primary_keys pk
304
309
  else
305
- td.primary_key pk, options.fetch(:id, :primary_key), **options.except(:comment)
310
+ td.primary_key pk, id, **options
306
311
  end
307
312
  end
308
313
 
309
314
  yield td if block_given?
310
315
 
311
- if options[:force]
312
- drop_table(table_name, **options, if_exists: true)
316
+ if force
317
+ drop_table(table_name, force: force, if_exists: true)
318
+ else
319
+ schema_cache.clear_data_source_cache!(table_name.to_s)
313
320
  end
314
321
 
315
322
  result = execute schema_creation.accept td
316
323
 
317
324
  unless supports_indexes_in_create?
318
325
  td.indexes.each do |column_name, index_options|
319
- add_index(table_name, column_name, index_options)
326
+ add_index(table_name, column_name, **index_options, if_not_exists: td.if_not_exists)
320
327
  end
321
328
  end
322
329
 
323
330
  if supports_comments? && !supports_comments_in_create?
324
- if table_comment = options[:comment].presence
331
+ if table_comment = td.comment.presence
325
332
  change_table_comment(table_name, table_comment)
326
333
  end
327
334
 
@@ -420,6 +427,12 @@ module ActiveRecord
420
427
  # t.column :name, :string, limit: 60
421
428
  # end
422
429
  #
430
+ # ====== Change type of a column
431
+ #
432
+ # change_table(:suppliers) do |t|
433
+ # t.change :metadata, :json
434
+ # end
435
+ #
423
436
  # ====== Add 2 integer columns
424
437
  #
425
438
  # change_table(:suppliers) do |t|
@@ -499,6 +512,7 @@ module ActiveRecord
499
512
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
500
513
  # In that case, +options+ and the block will be used by #create_table.
501
514
  def drop_table(table_name, **options)
515
+ schema_cache.clear_data_source_cache!(table_name.to_s)
502
516
  execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
503
517
  end
504
518
 
@@ -534,6 +548,9 @@ module ActiveRecord
534
548
  # column will have the same collation as the table.
535
549
  # * <tt>:comment</tt> -
536
550
  # Specifies the comment for the column. This option is ignored by some backends.
551
+ # * <tt>:if_not_exists</tt> -
552
+ # Specifies if the column already exists to not try to re-add it. This will avoid
553
+ # duplicate column errors.
537
554
  #
538
555
  # Note: The precision is the total number of significant digits,
539
556
  # and the scale is the number of digits that can be stored following
@@ -554,8 +571,6 @@ module ActiveRecord
554
571
  # but the maximum supported <tt>:precision</tt> is 16. No default.
555
572
  # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
556
573
  # Default is (38,0).
557
- # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
558
- # Default unknown.
559
574
  # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
560
575
  # Default (38,0).
561
576
  #
@@ -585,20 +600,37 @@ module ActiveRecord
585
600
  # # Defines a column with a database-specific type.
586
601
  # add_column(:shapes, :triangle, 'polygon')
587
602
  # # ALTER TABLE "shapes" ADD "triangle" polygon
603
+ #
604
+ # # Ignores the method call if the column exists
605
+ # add_column(:shapes, :triangle, 'polygon', if_not_exists: true)
588
606
  def add_column(table_name, column_name, type, **options)
607
+ return if options[:if_not_exists] == true && column_exists?(table_name, column_name, type)
608
+
589
609
  at = create_alter_table table_name
590
610
  at.add_column(column_name, type, **options)
591
611
  execute schema_creation.accept at
592
612
  end
593
613
 
614
+ def add_columns(table_name, *column_names, type:, **options) # :nodoc:
615
+ column_names.each do |column_name|
616
+ add_column(table_name, column_name, type, **options)
617
+ end
618
+ end
619
+
594
620
  # Removes the given columns from the table definition.
595
621
  #
596
622
  # remove_columns(:suppliers, :qualification, :experience)
597
623
  #
598
- def remove_columns(table_name, *column_names)
599
- raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
624
+ # +type+ and other column options can be passed to make migration reversible.
625
+ #
626
+ # remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
627
+ def remove_columns(table_name, *column_names, type: nil, **options)
628
+ if column_names.empty?
629
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
630
+ end
631
+
600
632
  column_names.each do |column_name|
601
- remove_column(table_name, column_name)
633
+ remove_column(table_name, column_name, type, **options)
602
634
  end
603
635
  end
604
636
 
@@ -610,7 +642,15 @@ module ActiveRecord
610
642
  # to provide these in a migration's +change+ method so it can be reverted.
611
643
  # In that case, +type+ and +options+ will be used by #add_column.
612
644
  # Indexes on the column are automatically removed.
645
+ #
646
+ # If the options provided include an +if_exists+ key, it will be used to check if the
647
+ # column does not exist. This will silently ignore the migration rather than raising
648
+ # if the column was already used.
649
+ #
650
+ # remove_column(:suppliers, :qualification, if_exists: true)
613
651
  def remove_column(table_name, column_name, type = nil, **options)
652
+ return if options[:if_exists] == true && !column_exists?(table_name, column_name)
653
+
614
654
  execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, **options)}"
615
655
  end
616
656
 
@@ -620,7 +660,7 @@ module ActiveRecord
620
660
  # change_column(:suppliers, :name, :string, limit: 80)
621
661
  # change_column(:accounts, :description, :text)
622
662
  #
623
- def change_column(table_name, column_name, type, options = {})
663
+ def change_column(table_name, column_name, type, **options)
624
664
  raise NotImplementedError, "change_column is not implemented"
625
665
  end
626
666
 
@@ -682,7 +722,17 @@ module ActiveRecord
682
722
  #
683
723
  # generates:
684
724
  #
685
- # CREATE INDEX suppliers_name_index ON suppliers(name)
725
+ # CREATE INDEX index_suppliers_on_name ON suppliers(name)
726
+ #
727
+ # ====== Creating a index which already exists
728
+ #
729
+ # add_index(:suppliers, :name, if_not_exists: true)
730
+ #
731
+ # generates:
732
+ #
733
+ # CREATE INDEX IF NOT EXISTS index_suppliers_on_name ON suppliers(name)
734
+ #
735
+ # Note: Not supported by MySQL.
686
736
  #
687
737
  # ====== Creating a unique index
688
738
  #
@@ -690,7 +740,7 @@ module ActiveRecord
690
740
  #
691
741
  # generates:
692
742
  #
693
- # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
743
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id)
694
744
  #
695
745
  # ====== Creating a named index
696
746
  #
@@ -720,7 +770,7 @@ module ActiveRecord
720
770
  #
721
771
  # ====== Creating an index with a sort order (desc or asc, asc is the default)
722
772
  #
723
- # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
773
+ # add_index(:accounts, [:branch_id, :party_id, :surname], name: 'by_branch_desc_party', order: {branch_id: :desc, party_id: :asc})
724
774
  #
725
775
  # generates:
726
776
  #
@@ -736,7 +786,7 @@ module ActiveRecord
736
786
  #
737
787
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
738
788
  #
739
- # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
789
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite.
740
790
  #
741
791
  # ====== Creating an index with a specific method
742
792
  #
@@ -782,9 +832,11 @@ module ActiveRecord
782
832
  # Concurrently adding an index is not supported in a transaction.
783
833
  #
784
834
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
785
- def add_index(table_name, column_name, options = {})
786
- index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, **options)
787
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
835
+ def add_index(table_name, column_name, **options)
836
+ index, algorithm, if_not_exists = add_index_options(table_name, column_name, **options)
837
+
838
+ create_index = CreateIndexDefinition.new(index, algorithm, if_not_exists)
839
+ execute schema_creation.accept(create_index)
788
840
  end
789
841
 
790
842
  # Removes the given index from the table.
@@ -805,6 +857,15 @@ module ActiveRecord
805
857
  #
806
858
  # remove_index :accounts, name: :by_branch_party
807
859
  #
860
+ # Removes the index on +branch_id+ named +by_branch_party+ in the +accounts+ table.
861
+ #
862
+ # remove_index :accounts, :branch_id, name: :by_branch_party
863
+ #
864
+ # Checks if the index exists before trying to remove it. Will silently ignore indexes that
865
+ # don't exist.
866
+ #
867
+ # remove_index :accounts, if_exists: true
868
+ #
808
869
  # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
809
870
  #
810
871
  # remove_index :accounts, name: :by_branch_party, algorithm: :concurrently
@@ -814,8 +875,11 @@ module ActiveRecord
814
875
  # Concurrently removing an index is not supported in a transaction.
815
876
  #
816
877
  # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
817
- def remove_index(table_name, options = {})
818
- index_name = index_name_for_remove(table_name, options)
878
+ def remove_index(table_name, column_name = nil, **options)
879
+ return if options[:if_exists] && !index_exists?(table_name, column_name, **options)
880
+
881
+ index_name = index_name_for_remove(table_name, column_name, options)
882
+
819
883
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
820
884
  end
821
885
 
@@ -826,6 +890,8 @@ module ActiveRecord
826
890
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
827
891
  #
828
892
  def rename_index(table_name, old_name, new_name)
893
+ old_name = old_name.to_s
894
+ new_name = new_name.to_s
829
895
  validate_index_length!(table_name, new_name)
830
896
 
831
897
  # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
@@ -867,7 +933,9 @@ module ActiveRecord
867
933
  # Add an appropriate index. Defaults to true.
868
934
  # See #add_index for usage of this option.
869
935
  # [<tt>:foreign_key</tt>]
870
- # Add an appropriate foreign key constraint. Defaults to false.
936
+ # Add an appropriate foreign key constraint. Defaults to false, pass true
937
+ # to add. In case the join table can't be inferred from the association
938
+ # pass <tt>:to_table</tt> with the appropriate table name.
871
939
  # [<tt>:polymorphic</tt>]
872
940
  # Whether an additional +_type+ column should be added. Defaults to false.
873
941
  # [<tt>:null</tt>]
@@ -899,7 +967,7 @@ module ActiveRecord
899
967
  #
900
968
  # ====== Create a supplier_id column and a foreign key to the firms table
901
969
  #
902
- # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
970
+ # add_reference(:products, :supplier, foreign_key: { to_table: :firms })
903
971
  #
904
972
  def add_reference(table_name, ref_name, **options)
905
973
  ReferenceDefinition.new(ref_name, **options).add_to(update_table_definition(table_name, self))
@@ -1061,6 +1129,60 @@ module ActiveRecord
1061
1129
  options
1062
1130
  end
1063
1131
 
1132
+ # Returns an array of check constraints for the given table.
1133
+ # The check constraints are represented as CheckConstraintDefinition objects.
1134
+ def check_constraints(table_name)
1135
+ raise NotImplementedError
1136
+ end
1137
+
1138
+ # Adds a new check constraint to the table. +expression+ is a String
1139
+ # representation of verifiable boolean condition.
1140
+ #
1141
+ # add_check_constraint :products, "price > 0", name: "price_check"
1142
+ #
1143
+ # generates:
1144
+ #
1145
+ # ALTER TABLE "products" ADD CONSTRAINT price_check CHECK (price > 0)
1146
+ #
1147
+ # The +options+ hash can include the following keys:
1148
+ # [<tt>:name</tt>]
1149
+ # The constraint name. Defaults to <tt>chk_rails_<identifier></tt>.
1150
+ # [<tt>:validate</tt>]
1151
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
1152
+ def add_check_constraint(table_name, expression, **options)
1153
+ return unless supports_check_constraints?
1154
+
1155
+ options = check_constraint_options(table_name, expression, options)
1156
+ at = create_alter_table(table_name)
1157
+ at.add_check_constraint(expression, options)
1158
+
1159
+ execute schema_creation.accept(at)
1160
+ end
1161
+
1162
+ def check_constraint_options(table_name, expression, options) # :nodoc:
1163
+ options = options.dup
1164
+ options[:name] ||= check_constraint_name(table_name, expression: expression, **options)
1165
+ options
1166
+ end
1167
+
1168
+ # Removes the given check constraint from the table.
1169
+ #
1170
+ # remove_check_constraint :products, name: "price_check"
1171
+ #
1172
+ # The +expression+ parameter will be ignored if present. It can be helpful
1173
+ # to provide this in a migration's +change+ method so it can be reverted.
1174
+ # In that case, +expression+ will be used by #add_check_constraint.
1175
+ def remove_check_constraint(table_name, expression = nil, **options)
1176
+ return unless supports_check_constraints?
1177
+
1178
+ chk_name_to_delete = check_constraint_for!(table_name, expression: expression, **options).name
1179
+
1180
+ at = create_alter_table(table_name)
1181
+ at.drop_check_constraint(chk_name_to_delete)
1182
+
1183
+ execute schema_creation.accept(at)
1184
+ end
1185
+
1064
1186
  def dump_schema_information # :nodoc:
1065
1187
  versions = schema_migration.all_versions
1066
1188
  insert_versions_sql(versions) if versions.any?
@@ -1070,13 +1192,7 @@ module ActiveRecord
1070
1192
  { primary_key: true }
1071
1193
  end
1072
1194
 
1073
- def assume_migrated_upto_version(version, migrations_paths = nil)
1074
- unless migrations_paths.nil?
1075
- ActiveSupport::Deprecation.warn(<<~MSG.squish)
1076
- Passing migrations_paths to #assume_migrated_upto_version is deprecated and will be removed in Rails 6.1.
1077
- MSG
1078
- end
1079
-
1195
+ def assume_migrated_upto_version(version)
1080
1196
  version = version.to_i
1081
1197
  sm_table = quote_table_name(schema_migration.table_name)
1082
1198
 
@@ -1169,36 +1285,43 @@ module ActiveRecord
1169
1285
  Table.new(table_name, base)
1170
1286
  end
1171
1287
 
1172
- def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1173
- column_names = index_column_names(column_name)
1288
+ def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
1289
+ options.assert_valid_keys(:unique, :length, :order, :opclass, :where, :type, :using, :comment, :algorithm)
1174
1290
 
1175
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
1291
+ column_names = index_column_names(column_name)
1176
1292
 
1177
- index_type = options[:type].to_s if options.key?(:type)
1178
- index_type ||= options[:unique] ? "UNIQUE" : ""
1179
- index_name = options[:name].to_s if options.key?(:name)
1293
+ index_name = name&.to_s
1180
1294
  index_name ||= index_name(table_name, column_names)
1181
1295
 
1182
- if options.key?(:algorithm)
1183
- algorithm = index_algorithms.fetch(options[:algorithm]) {
1184
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
1185
- }
1186
- end
1187
-
1188
- using = "USING #{options[:using]}" if options[:using].present?
1189
-
1190
- if supports_partial_index?
1191
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
1192
- end
1296
+ validate_index_length!(table_name, index_name, internal)
1297
+
1298
+ index = IndexDefinition.new(
1299
+ table_name, index_name,
1300
+ options[:unique],
1301
+ column_names,
1302
+ lengths: options[:length] || {},
1303
+ orders: options[:order] || {},
1304
+ opclasses: options[:opclass] || {},
1305
+ where: options[:where],
1306
+ type: options[:type],
1307
+ using: options[:using],
1308
+ comment: options[:comment]
1309
+ )
1310
+
1311
+ [index, index_algorithm(options[:algorithm]), if_not_exists]
1312
+ end
1193
1313
 
1194
- validate_index_length!(table_name, index_name, options.fetch(:internal, false))
1314
+ def index_algorithm(algorithm) # :nodoc:
1315
+ index_algorithms.fetch(algorithm) do
1316
+ raise ArgumentError, "Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}"
1317
+ end if algorithm
1318
+ end
1195
1319
 
1196
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
1197
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1320
+ def quoted_columns_for_index(column_names, options) # :nodoc:
1321
+ quoted_columns = column_names.each_with_object({}) do |name, result|
1322
+ result[name.to_sym] = quote_column_name(name).dup
1198
1323
  end
1199
- index_columns = quoted_columns_for_index(column_names, **options).join(", ")
1200
-
1201
- [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1324
+ add_options_for_index_columns(quoted_columns, **options).values.join(", ")
1202
1325
  end
1203
1326
 
1204
1327
  def options_include_default?(options)
@@ -1259,24 +1382,13 @@ module ActiveRecord
1259
1382
  quoted_columns
1260
1383
  end
1261
1384
 
1262
- def quoted_columns_for_index(column_names, **options)
1263
- return [column_names] if column_names.is_a?(String)
1264
-
1265
- quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
1266
- add_options_for_index_columns(quoted_columns, **options).values
1267
- end
1268
-
1269
- def index_name_for_remove(table_name, options = {})
1270
- return options[:name] if can_remove_index_by_name?(options)
1385
+ def index_name_for_remove(table_name, column_name, options)
1386
+ return options[:name] if can_remove_index_by_name?(column_name, options)
1271
1387
 
1272
1388
  checks = []
1273
1389
 
1274
- if options.is_a?(Hash)
1275
- checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1276
- column_names = index_column_names(options[:column])
1277
- else
1278
- column_names = index_column_names(options)
1279
- end
1390
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1391
+ column_names = index_column_names(column_name || options[:column])
1280
1392
 
1281
1393
  if column_names.present?
1282
1394
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
@@ -1322,14 +1434,18 @@ module ActiveRecord
1322
1434
  SchemaCreation.new(self)
1323
1435
  end
1324
1436
 
1325
- def create_table_definition(*args, **options)
1326
- TableDefinition.new(self, *args, **options)
1437
+ def create_table_definition(name, **options)
1438
+ TableDefinition.new(self, name, **options)
1327
1439
  end
1328
1440
 
1329
1441
  def create_alter_table(name)
1330
1442
  AlterTable.new create_table_definition(name)
1331
1443
  end
1332
1444
 
1445
+ def extract_table_options!(options)
1446
+ options.extract!(:temporary, :if_not_exists, :options, :as, :comment, :charset, :collation)
1447
+ end
1448
+
1333
1449
  def fetch_type_metadata(sql_type)
1334
1450
  cast_type = lookup_cast_type(sql_type)
1335
1451
  SqlTypeMetadata.new(
@@ -1390,11 +1506,30 @@ module ActiveRecord
1390
1506
  end
1391
1507
  end
1392
1508
 
1393
- def validate_index_length!(table_name, new_name, internal = false)
1394
- max_index_length = internal ? index_name_length : allowed_index_name_length
1509
+ def check_constraint_name(table_name, **options)
1510
+ options.fetch(:name) do
1511
+ expression = options.fetch(:expression)
1512
+ identifier = "#{table_name}_#{expression}_chk"
1513
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1395
1514
 
1396
- if new_name.length > max_index_length
1397
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1515
+ "chk_rails_#{hashed_identifier}"
1516
+ end
1517
+ end
1518
+
1519
+ def check_constraint_for(table_name, **options)
1520
+ return unless supports_check_constraints?
1521
+ chk_name = check_constraint_name(table_name, **options)
1522
+ check_constraints(table_name).detect { |chk| chk.name == chk_name }
1523
+ end
1524
+
1525
+ def check_constraint_for!(table_name, expression: nil, **options)
1526
+ check_constraint_for(table_name, expression: expression, **options) ||
1527
+ raise(ArgumentError, "Table '#{table_name}' has no check constraint for #{expression || options}")
1528
+ end
1529
+
1530
+ def validate_index_length!(table_name, new_name, internal = false)
1531
+ if new_name.length > index_name_length
1532
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{index_name_length} characters"
1398
1533
  end
1399
1534
  end
1400
1535
 
@@ -1407,8 +1542,8 @@ module ActiveRecord
1407
1542
  end
1408
1543
  alias :extract_new_comment_value :extract_new_default_value
1409
1544
 
1410
- def can_remove_index_by_name?(options)
1411
- options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1545
+ def can_remove_index_by_name?(column_name, options)
1546
+ column_name.nil? && options.key?(:name) && options.except(:name, :algorithm).empty?
1412
1547
  end
1413
1548
 
1414
1549
  def bulk_change_table(table_name, operations)
@@ -1442,6 +1577,10 @@ module ActiveRecord
1442
1577
  schema_creation.accept(AddColumnDefinition.new(cd))
1443
1578
  end
1444
1579
 
1580
+ def rename_column_sql(table_name, column_name, new_column_name)
1581
+ "RENAME COLUMN #{quote_column_name(column_name)} TO #{quote_column_name(new_column_name)}"
1582
+ end
1583
+
1445
1584
  def remove_column_for_alter(table_name, column_name, type = nil, **options)
1446
1585
  "DROP COLUMN #{quote_column_name(column_name)}"
1447
1586
  end