activerecord 4.2.11 → 5.2.4.1

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +580 -1626
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +10 -11
  5. data/examples/performance.rb +32 -31
  6. data/examples/simple.rb +5 -4
  7. data/lib/active_record/aggregations.rb +263 -249
  8. data/lib/active_record/association_relation.rb +11 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +77 -43
  11. data/lib/active_record/associations/association_scope.rb +106 -133
  12. data/lib/active_record/associations/belongs_to_association.rb +52 -41
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -8
  14. data/lib/active_record/associations/builder/association.rb +29 -38
  15. data/lib/active_record/associations/builder/belongs_to.rb +77 -30
  16. data/lib/active_record/associations/builder/collection_association.rb +9 -22
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +42 -35
  18. data/lib/active_record/associations/builder/has_many.rb +6 -4
  19. data/lib/active_record/associations/builder/has_one.rb +13 -6
  20. data/lib/active_record/associations/builder/singular_association.rb +15 -11
  21. data/lib/active_record/associations/collection_association.rb +139 -280
  22. data/lib/active_record/associations/collection_proxy.rb +231 -133
  23. data/lib/active_record/associations/foreign_association.rb +3 -1
  24. data/lib/active_record/associations/has_many_association.rb +34 -89
  25. data/lib/active_record/associations/has_many_through_association.rb +49 -76
  26. data/lib/active_record/associations/has_one_association.rb +38 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +40 -87
  29. data/lib/active_record/associations/join_dependency/join_base.rb +10 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +12 -12
  31. data/lib/active_record/associations/join_dependency.rb +133 -159
  32. data/lib/active_record/associations/preloader/association.rb +85 -120
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +81 -91
  35. data/lib/active_record/associations/singular_association.rb +27 -34
  36. data/lib/active_record/associations/through_association.rb +38 -18
  37. data/lib/active_record/associations.rb +1732 -1597
  38. data/lib/active_record/attribute_assignment.rb +58 -182
  39. data/lib/active_record/attribute_decorators.rb +39 -15
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +10 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -135
  42. data/lib/active_record/attribute_methods/primary_key.rb +86 -71
  43. data/lib/active_record/attribute_methods/query.rb +4 -2
  44. data/lib/active_record/attribute_methods/read.rb +45 -63
  45. data/lib/active_record/attribute_methods/serialization.rb +40 -20
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +58 -36
  47. data/lib/active_record/attribute_methods/write.rb +30 -45
  48. data/lib/active_record/attribute_methods.rb +166 -109
  49. data/lib/active_record/attributes.rb +201 -82
  50. data/lib/active_record/autosave_association.rb +94 -36
  51. data/lib/active_record/base.rb +57 -44
  52. data/lib/active_record/callbacks.rb +97 -57
  53. data/lib/active_record/coders/json.rb +3 -1
  54. data/lib/active_record/coders/yaml_column.rb +24 -12
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -290
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +237 -90
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +71 -21
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +118 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +5 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +318 -217
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +81 -36
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +570 -228
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +138 -70
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +325 -202
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +542 -601
  69. data/lib/active_record/connection_adapters/column.rb +50 -41
  70. data/lib/active_record/connection_adapters/connection_specification.rb +147 -135
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +41 -180
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +45 -114
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +50 -58
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +10 -6
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +2 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +4 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +5 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +13 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +9 -22
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +5 -3
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +31 -19
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +2 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +3 -11
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +5 -7
  99. data/lib/active_record/connection_adapters/postgresql/oid/{integer.rb → oid.rb} +6 -2
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +33 -11
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +52 -34
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +4 -5
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +55 -53
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +3 -1
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +3 -1
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +23 -25
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +107 -47
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +27 -14
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +144 -90
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +462 -284
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +12 -8
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +432 -323
  117. data/lib/active_record/connection_adapters/schema_cache.rb +48 -24
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +269 -308
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +40 -27
  128. data/lib/active_record/core.rb +178 -198
  129. data/lib/active_record/counter_cache.rb +79 -36
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +87 -105
  132. data/lib/active_record/enum.rb +135 -88
  133. data/lib/active_record/errors.rb +179 -52
  134. data/lib/active_record/explain.rb +23 -11
  135. data/lib/active_record/explain_registry.rb +4 -2
  136. data/lib/active_record/explain_subscriber.rb +10 -5
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +188 -132
  139. data/lib/active_record/gem_version.rb +5 -3
  140. data/lib/active_record/inheritance.rb +148 -112
  141. data/lib/active_record/integration.rb +70 -28
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +21 -3
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +88 -96
  146. data/lib/active_record/locking/pessimistic.rb +15 -3
  147. data/lib/active_record/log_subscriber.rb +95 -33
  148. data/lib/active_record/migration/command_recorder.rb +133 -90
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +8 -6
  151. data/lib/active_record/migration.rb +581 -282
  152. data/lib/active_record/model_schema.rb +290 -111
  153. data/lib/active_record/nested_attributes.rb +264 -222
  154. data/lib/active_record/no_touching.rb +7 -1
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +347 -119
  157. data/lib/active_record/query_cache.rb +13 -24
  158. data/lib/active_record/querying.rb +19 -17
  159. data/lib/active_record/railtie.rb +94 -32
  160. data/lib/active_record/railties/console_sandbox.rb +2 -0
  161. data/lib/active_record/railties/controller_runtime.rb +9 -3
  162. data/lib/active_record/railties/databases.rake +149 -156
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +414 -267
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +204 -55
  167. data/lib/active_record/relation/calculations.rb +256 -248
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +288 -239
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +86 -86
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -24
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +7 -1
  179. data/lib/active_record/relation/predicate_builder.rb +116 -119
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +448 -393
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +11 -13
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +287 -340
  187. data/lib/active_record/result.rb +54 -36
  188. data/lib/active_record/runtime_registry.rb +6 -4
  189. data/lib/active_record/sanitization.rb +155 -124
  190. data/lib/active_record/schema.rb +30 -24
  191. data/lib/active_record/schema_dumper.rb +91 -87
  192. data/lib/active_record/schema_migration.rb +19 -16
  193. data/lib/active_record/scoping/default.rb +102 -85
  194. data/lib/active_record/scoping/named.rb +81 -32
  195. data/lib/active_record/scoping.rb +45 -26
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +5 -5
  198. data/lib/active_record/statement_cache.rb +45 -35
  199. data/lib/active_record/store.rb +42 -36
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +134 -96
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +56 -100
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +83 -41
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +44 -16
  206. data/lib/active_record/timestamp.rb +70 -38
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +199 -124
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +4 -45
  212. data/lib/active_record/type/date_time.rb +4 -49
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +5 -3
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +24 -15
  218. data/lib/active_record/type/text.rb +2 -2
  219. data/lib/active_record/type/time.rb +11 -16
  220. data/lib/active_record/type/type_map.rb +15 -17
  221. data/lib/active_record/type/unsigned_integer.rb +9 -7
  222. data/lib/active_record/type.rb +79 -23
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +13 -4
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +14 -13
  230. data/lib/active_record/validations/uniqueness.rb +40 -41
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +34 -22
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +43 -35
  237. data/lib/rails/generators/active_record/migration/templates/{create_table_migration.rb → create_table_migration.rb.tt} +8 -3
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -1
  239. data/lib/rails/generators/active_record/migration.rb +18 -1
  240. data/lib/rails/generators/active_record/model/model_generator.rb +18 -22
  241. data/lib/rails/generators/active_record/model/templates/{model.rb → model.rb.tt} +3 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
  243. data/lib/rails/generators/active_record.rb +7 -5
  244. metadata +72 -50
  245. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  246. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  247. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  248. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  249. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  250. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  251. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  252. data/lib/active_record/attribute.rb +0 -163
  253. data/lib/active_record/attribute_set/builder.rb +0 -106
  254. data/lib/active_record/attribute_set.rb +0 -81
  255. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -498
  256. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  257. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  258. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  259. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  260. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  261. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  262. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  263. data/lib/active_record/type/big_integer.rb +0 -13
  264. data/lib/active_record/type/binary.rb +0 -50
  265. data/lib/active_record/type/boolean.rb +0 -31
  266. data/lib/active_record/type/decimal.rb +0 -64
  267. data/lib/active_record/type/decorator.rb +0 -14
  268. data/lib/active_record/type/float.rb +0 -19
  269. data/lib/active_record/type/integer.rb +0 -59
  270. data/lib/active_record/type/mutable.rb +0 -16
  271. data/lib/active_record/type/numeric.rb +0 -36
  272. data/lib/active_record/type/string.rb +0 -40
  273. data/lib/active_record/type/time_value.rb +0 -38
  274. data/lib/active_record/type/value.rb +0 -110
@@ -1,6 +1,8 @@
1
- require 'active_record/migration/join_table'
2
- require 'active_support/core_ext/string/access'
3
- require 'digest'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/migration/join_table"
4
+ require "active_support/core_ext/string/access"
5
+ require "digest/sha2"
4
6
 
5
7
  module ActiveRecord
6
8
  module ConnectionAdapters # :nodoc:
@@ -14,15 +16,26 @@ module ActiveRecord
14
16
  {}
15
17
  end
16
18
 
19
+ def table_options(table_name)
20
+ nil
21
+ end
22
+
23
+ # Returns the table comment that's stored in database metadata.
24
+ def table_comment(table_name)
25
+ nil
26
+ end
27
+
17
28
  # Truncates a table alias according to the limits of the current adapter.
18
29
  def table_alias_for(table_name)
19
- table_name[0...table_alias_length].tr('.', '_')
30
+ table_name[0...table_alias_length].tr(".", "_")
20
31
  end
21
32
 
22
33
  # Returns the relation names useable to back Active Record models.
23
- # For most adapters this means all tables and views.
34
+ # For most adapters this means all #tables and #views.
24
35
  def data_sources
25
- tables
36
+ query_values(data_source_sql, "SCHEMA")
37
+ rescue NotImplementedError
38
+ tables | views
26
39
  end
27
40
 
28
41
  # Checks to see if the data source +name+ exists on the database.
@@ -30,19 +43,45 @@ module ActiveRecord
30
43
  # data_source_exists?(:ebooks)
31
44
  #
32
45
  def data_source_exists?(name)
46
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
47
+ rescue NotImplementedError
33
48
  data_sources.include?(name.to_s)
34
49
  end
35
50
 
51
+ # Returns an array of table names defined in the database.
52
+ def tables
53
+ query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA")
54
+ end
55
+
36
56
  # Checks to see if the table +table_name+ exists on the database.
37
57
  #
38
58
  # table_exists?(:developers)
39
59
  #
40
60
  def table_exists?(table_name)
61
+ query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present?
62
+ rescue NotImplementedError
41
63
  tables.include?(table_name.to_s)
42
64
  end
43
65
 
66
+ # Returns an array of view names defined in the database.
67
+ def views
68
+ query_values(data_source_sql(type: "VIEW"), "SCHEMA")
69
+ end
70
+
71
+ # Checks to see if the view +view_name+ exists on the database.
72
+ #
73
+ # view_exists?(:ebooks)
74
+ #
75
+ def view_exists?(view_name)
76
+ query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present?
77
+ rescue NotImplementedError
78
+ views.include?(view_name.to_s)
79
+ end
80
+
44
81
  # Returns an array of indexes for the given table.
45
- # def indexes(table_name, name = nil) end
82
+ def indexes(table_name)
83
+ raise NotImplementedError, "#indexes is not implemented"
84
+ end
46
85
 
47
86
  # Checks to see if an index exists on a table for a given index definition.
48
87
  #
@@ -60,18 +99,21 @@ module ActiveRecord
60
99
  #
61
100
  def index_exists?(table_name, column_name, options = {})
62
101
  column_names = Array(column_name).map(&:to_s)
63
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
64
102
  checks = []
65
- checks << lambda { |i| i.name == index_name }
66
- checks << lambda { |i| i.columns == column_names }
103
+ checks << lambda { |i| Array(i.columns) == column_names }
67
104
  checks << lambda { |i| i.unique } if options[:unique]
105
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
68
106
 
69
107
  indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
70
108
  end
71
109
 
72
- # Returns an array of Column objects for the table specified by +table_name+.
73
- # See the concrete implementation for details on the expected parameter values.
74
- def columns(table_name) end
110
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
111
+ def columns(table_name)
112
+ table_name = table_name.to_s
113
+ column_definitions(table_name).map do |field|
114
+ new_column_from_field(table_name, field)
115
+ end
116
+ end
75
117
 
76
118
  # Checks to see if a column exists in a given table.
77
119
  #
@@ -89,19 +131,27 @@ module ActiveRecord
89
131
  #
90
132
  def column_exists?(table_name, column_name, type = nil, options = {})
91
133
  column_name = column_name.to_s
92
- columns(table_name).any?{ |c| c.name == column_name &&
93
- (!type || c.type == type) &&
94
- (!options.key?(:limit) || c.limit == options[:limit]) &&
95
- (!options.key?(:precision) || c.precision == options[:precision]) &&
96
- (!options.key?(:scale) || c.scale == options[:scale]) &&
97
- (!options.key?(:default) || c.default == options[:default]) &&
98
- (!options.key?(:null) || c.null == options[:null]) }
134
+ checks = []
135
+ checks << lambda { |c| c.name == column_name }
136
+ checks << lambda { |c| c.type == type } if type
137
+ column_options_keys.each do |attr|
138
+ checks << lambda { |c| c.send(attr) == options[attr] } if options.key?(attr)
139
+ end
140
+
141
+ columns(table_name).any? { |c| checks.all? { |check| check[c] } }
142
+ end
143
+
144
+ # Returns just a table's primary key
145
+ def primary_key(table_name)
146
+ pk = primary_keys(table_name)
147
+ pk = pk.first unless pk.size > 1
148
+ pk
99
149
  end
100
150
 
101
151
  # Creates a new table with the name +table_name+. +table_name+ may either
102
152
  # be a String or a Symbol.
103
153
  #
104
- # There are two ways to work with +create_table+. You can use the block
154
+ # There are two ways to work with #create_table. You can use the block
105
155
  # form or the regular form, like this:
106
156
  #
107
157
  # === Block form
@@ -133,13 +183,18 @@ module ActiveRecord
133
183
  # The +options+ hash can include the following keys:
134
184
  # [<tt>:id</tt>]
135
185
  # Whether to automatically add a primary key column. Defaults to true.
136
- # Join tables for +has_and_belongs_to_many+ should set it to false.
186
+ # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
187
+ #
188
+ # A Symbol can be used to specify the type of the generated primary key column.
137
189
  # [<tt>:primary_key</tt>]
138
190
  # The name of the primary key, if one is to be added automatically.
139
- # Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
191
+ # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
192
+ #
193
+ # If an array is passed, a composite primary key will be created.
140
194
  #
141
195
  # Note that Active Record models will automatically detect their
142
- # primary key. This can be avoided by using +self.primary_key=+ on the model
196
+ # primary key. This can be avoided by using
197
+ # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
143
198
  # to define the key explicitly.
144
199
  #
145
200
  # [<tt>:options</tt>]
@@ -161,7 +216,7 @@ module ActiveRecord
161
216
  # generates:
162
217
  #
163
218
  # CREATE TABLE suppliers (
164
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
219
+ # id bigint auto_increment PRIMARY KEY
165
220
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
166
221
  #
167
222
  # ====== Rename the primary key column
@@ -173,22 +228,52 @@ module ActiveRecord
173
228
  # generates:
174
229
  #
175
230
  # CREATE TABLE objects (
176
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
231
+ # guid bigint auto_increment PRIMARY KEY,
177
232
  # name varchar(80)
178
233
  # )
179
234
  #
235
+ # ====== Change the primary key column type
236
+ #
237
+ # create_table(:tags, id: :string) do |t|
238
+ # t.column :label, :string
239
+ # end
240
+ #
241
+ # generates:
242
+ #
243
+ # CREATE TABLE tags (
244
+ # id varchar PRIMARY KEY,
245
+ # label varchar
246
+ # )
247
+ #
248
+ # ====== Create a composite primary key
249
+ #
250
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
251
+ # t.belongs_to :product
252
+ # t.belongs_to :client
253
+ # end
254
+ #
255
+ # generates:
256
+ #
257
+ # CREATE TABLE order (
258
+ # product_id bigint NOT NULL,
259
+ # client_id bigint NOT NULL
260
+ # );
261
+ #
262
+ # ALTER TABLE ONLY "orders"
263
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
264
+ #
180
265
  # ====== Do not add a primary key column
181
266
  #
182
267
  # create_table(:categories_suppliers, id: false) do |t|
183
- # t.column :category_id, :integer
184
- # t.column :supplier_id, :integer
268
+ # t.column :category_id, :bigint
269
+ # t.column :supplier_id, :bigint
185
270
  # end
186
271
  #
187
272
  # generates:
188
273
  #
189
274
  # CREATE TABLE categories_suppliers (
190
- # category_id int,
191
- # supplier_id int
275
+ # category_id bigint,
276
+ # supplier_id bigint
192
277
  # )
193
278
  #
194
279
  # ====== Create a temporary table based on a query
@@ -202,33 +287,41 @@ module ActiveRecord
202
287
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
203
288
  #
204
289
  # See also TableDefinition#column for details on how to create columns.
205
- def create_table(table_name, options = {})
206
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
290
+ def create_table(table_name, comment: nil, **options)
291
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
207
292
 
208
293
  if options[:id] != false && !options[:as]
209
294
  pk = options.fetch(:primary_key) do
210
295
  Base.get_primary_key table_name.to_s.singularize
211
296
  end
212
297
 
213
- td.primary_key pk, options.fetch(:id, :primary_key), options
298
+ if pk.is_a?(Array)
299
+ td.primary_keys pk
300
+ else
301
+ td.primary_key pk, options.fetch(:id, :primary_key), options
302
+ end
214
303
  end
215
304
 
216
305
  yield td if block_given?
217
306
 
218
- if options[:force] && table_exists?(table_name)
219
- drop_table(table_name, options)
307
+ if options[:force]
308
+ drop_table(table_name, options.merge(if_exists: true))
220
309
  end
221
310
 
222
311
  result = execute schema_creation.accept td
223
312
 
224
313
  unless supports_indexes_in_create?
225
- td.indexes.each_pair do |column_name, index_options|
314
+ td.indexes.each do |column_name, index_options|
226
315
  add_index(table_name, column_name, index_options)
227
316
  end
228
317
  end
229
318
 
230
- td.foreign_keys.each do |other_table_name, foreign_key_options|
231
- add_foreign_key(table_name, other_table_name, foreign_key_options)
319
+ if supports_comments? && !supports_comments_in_create?
320
+ change_table_comment(table_name, comment) if comment.present?
321
+
322
+ td.columns.each do |column|
323
+ change_column_comment(table_name, column.name, column.comment) if column.comment.present?
324
+ end
232
325
  end
233
326
 
234
327
  result
@@ -240,9 +333,9 @@ module ActiveRecord
240
333
  # # Creates a table called 'assemblies_parts' with no id.
241
334
  # create_join_table(:assemblies, :parts)
242
335
  #
243
- # You can pass a +options+ hash can include the following keys:
336
+ # You can pass an +options+ hash which can include the following keys:
244
337
  # [<tt>:table_name</tt>]
245
- # Sets the table name overriding the default
338
+ # Sets the table name, overriding the default.
246
339
  # [<tt>:column_options</tt>]
247
340
  # Any extra options you want appended to the columns definition.
248
341
  # [<tt>:options</tt>]
@@ -253,7 +346,7 @@ module ActiveRecord
253
346
  # Set to true to drop the table before creating it.
254
347
  # Defaults to false.
255
348
  #
256
- # Note that +create_join_table+ does not create any indices by default; you can use
349
+ # Note that #create_join_table does not create any indices by default; you can use
257
350
  # its block form to do so yourself:
258
351
  #
259
352
  # create_join_table :products, :categories do |t|
@@ -268,31 +361,30 @@ module ActiveRecord
268
361
  # generates:
269
362
  #
270
363
  # CREATE TABLE assemblies_parts (
271
- # assembly_id int NOT NULL,
272
- # part_id int NOT NULL,
364
+ # assembly_id bigint NOT NULL,
365
+ # part_id bigint NOT NULL,
273
366
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
274
367
  #
275
- def create_join_table(table_1, table_2, options = {})
368
+ def create_join_table(table_1, table_2, column_options: {}, **options)
276
369
  join_table_name = find_join_table_name(table_1, table_2, options)
277
370
 
278
- column_options = options.delete(:column_options) || {}
279
- column_options.reverse_merge!(null: false)
371
+ column_options.reverse_merge!(null: false, index: false)
280
372
 
281
- t1_column, t2_column = [table_1, table_2].map{ |t| t.to_s.singularize.foreign_key }
373
+ t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
282
374
 
283
375
  create_table(join_table_name, options.merge!(id: false)) do |td|
284
- td.integer t1_column, column_options
285
- td.integer t2_column, column_options
376
+ td.references t1_ref, column_options
377
+ td.references t2_ref, column_options
286
378
  yield td if block_given?
287
379
  end
288
380
  end
289
381
 
290
382
  # Drops the join table specified by the given arguments.
291
- # See +create_join_table+ for details.
383
+ # See #create_join_table for details.
292
384
  #
293
385
  # Although this command ignores the block if one is given, it can be helpful
294
386
  # to provide one in a migration's +change+ method so it can be reverted.
295
- # In that case, the block will be used by create_join_table.
387
+ # In that case, the block will be used by #create_join_table.
296
388
  def drop_join_table(table_1, table_2, options = {})
297
389
  join_table_name = find_join_table_name(table_1, table_2, options)
298
390
  drop_table(join_table_name)
@@ -310,10 +402,12 @@ module ActiveRecord
310
402
  # [<tt>:bulk</tt>]
311
403
  # Set this to true to make this a bulk alter query, such as
312
404
  #
313
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
405
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
314
406
  #
315
407
  # Defaults to false.
316
408
  #
409
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
410
+ #
317
411
  # ====== Add a column
318
412
  #
319
413
  # change_table(:suppliers) do |t|
@@ -338,7 +432,7 @@ module ActiveRecord
338
432
  # t.references :company
339
433
  # end
340
434
  #
341
- # Creates a <tt>company_id(integer)</tt> column.
435
+ # Creates a <tt>company_id(bigint)</tt> column.
342
436
  #
343
437
  # ====== Add a polymorphic foreign key column
344
438
  #
@@ -346,7 +440,7 @@ module ActiveRecord
346
440
  # t.belongs_to :company, polymorphic: true
347
441
  # end
348
442
  #
349
- # Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> columns.
443
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
350
444
  #
351
445
  # ====== Remove a column
352
446
  #
@@ -367,7 +461,7 @@ module ActiveRecord
367
461
  # t.remove_index :company_id
368
462
  # end
369
463
  #
370
- # See also Table for details on all of the various column transformation.
464
+ # See also Table for details on all of the various column transformations.
371
465
  def change_table(table_name, options = {})
372
466
  if supports_bulk_alter? && options[:bulk]
373
467
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
@@ -391,16 +485,96 @@ module ActiveRecord
391
485
  # [<tt>:force</tt>]
392
486
  # Set to +:cascade+ to drop dependent objects as well.
393
487
  # Defaults to false.
488
+ # [<tt>:if_exists</tt>]
489
+ # Set to +true+ to only drop the table if it exists.
490
+ # Defaults to false.
394
491
  #
395
492
  # Although this command ignores most +options+ and the block if one is given,
396
493
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
397
- # In that case, +options+ and the block will be used by create_table.
494
+ # In that case, +options+ and the block will be used by #create_table.
398
495
  def drop_table(table_name, options = {})
399
- execute "DROP TABLE #{quote_table_name(table_name)}"
496
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
400
497
  end
401
498
 
402
- # Adds a new column to the named table.
403
- # See TableDefinition#column for details of the options you can use.
499
+ # Add a new +type+ column named +column_name+ to +table_name+.
500
+ #
501
+ # The +type+ parameter is normally one of the migrations native types,
502
+ # which is one of the following:
503
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
504
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
505
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
506
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
507
+ #
508
+ # You may use a type not in this list as long as it is supported by your
509
+ # database (for example, "polygon" in MySQL), but this will not be database
510
+ # agnostic and should usually be avoided.
511
+ #
512
+ # Available options are (none of these exists by default):
513
+ # * <tt>:limit</tt> -
514
+ # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
515
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
516
+ # This option is ignored by some backends.
517
+ # * <tt>:default</tt> -
518
+ # The column's default value. Use +nil+ for +NULL+.
519
+ # * <tt>:null</tt> -
520
+ # Allows or disallows +NULL+ values in the column.
521
+ # * <tt>:precision</tt> -
522
+ # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
523
+ # * <tt>:scale</tt> -
524
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
525
+ # * <tt>:comment</tt> -
526
+ # Specifies the comment for the column. This option is ignored by some backends.
527
+ #
528
+ # Note: The precision is the total number of significant digits,
529
+ # and the scale is the number of digits that can be stored following
530
+ # the decimal point. For example, the number 123.45 has a precision of 5
531
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
532
+ # range from -999.99 to 999.99.
533
+ #
534
+ # Please be aware of different RDBMS implementations behavior with
535
+ # <tt>:decimal</tt> columns:
536
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
537
+ # <tt>:precision</tt>, and makes no comments about the requirements of
538
+ # <tt>:precision</tt>.
539
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
540
+ # Default is (10,0).
541
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
542
+ # <tt>:scale</tt> [0..infinity]. No default.
543
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
544
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
545
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
546
+ # Default is (38,0).
547
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
548
+ # Default unknown.
549
+ # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
550
+ # Default (38,0).
551
+ #
552
+ # == Examples
553
+ #
554
+ # add_column(:users, :picture, :binary, limit: 2.megabytes)
555
+ # # ALTER TABLE "users" ADD "picture" blob(2097152)
556
+ #
557
+ # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
558
+ # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
559
+ #
560
+ # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
561
+ # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
562
+ #
563
+ # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
564
+ # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
565
+ #
566
+ # # While :scale defaults to zero on most databases, it
567
+ # # probably wouldn't hurt to include it.
568
+ # add_column(:measurements, :huge_integer, :decimal, precision: 30)
569
+ # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
570
+ #
571
+ # # Defines a column that stores an array of a type.
572
+ # add_column(:users, :skills, :text, array: true)
573
+ # # ALTER TABLE "users" ADD "skills" text[]
574
+ #
575
+ # # Defines a column with a database-specific type.
576
+ # add_column(:shapes, :triangle, 'polygon')
577
+ # # ALTER TABLE "shapes" ADD "triangle" polygon
404
578
  def add_column(table_name, column_name, type, options = {})
405
579
  at = create_alter_table table_name
406
580
  at.add_column(column_name, type, options)
@@ -424,9 +598,9 @@ module ActiveRecord
424
598
  #
425
599
  # The +type+ and +options+ parameters will be ignored if present. It can be helpful
426
600
  # to provide these in a migration's +change+ method so it can be reverted.
427
- # In that case, +type+ and +options+ will be used by add_column.
601
+ # In that case, +type+ and +options+ will be used by #add_column.
428
602
  def remove_column(table_name, column_name, type = nil, options = {})
429
- execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
603
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
430
604
  end
431
605
 
432
606
  # Changes the column's definition according to the new options.
@@ -448,11 +622,16 @@ module ActiveRecord
448
622
  #
449
623
  # change_column_default(:users, :email, nil)
450
624
  #
451
- def change_column_default(table_name, column_name, default)
625
+ # Passing a hash containing +:from+ and +:to+ will make this change
626
+ # reversible in migration:
627
+ #
628
+ # change_column_default(:posts, :state, from: nil, to: "draft")
629
+ #
630
+ def change_column_default(table_name, column_name, default_or_changes)
452
631
  raise NotImplementedError, "change_column_default is not implemented"
453
632
  end
454
633
 
455
- # Sets or removes a +NOT NULL+ constraint on a column. The +null+ flag
634
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
456
635
  # indicates whether the value can be +NULL+. For example
457
636
  #
458
637
  # change_column_null(:users, :nickname, false)
@@ -464,7 +643,7 @@ module ActiveRecord
464
643
  # allows them to be +NULL+ (drops the constraint).
465
644
  #
466
645
  # The method accepts an optional fourth argument to replace existing
467
- # +NULL+s with some other value. Use that one when enabling the
646
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
468
647
  # constraint if needed, since otherwise those rows would not be valid.
469
648
  #
470
649
  # Please note the fourth argument does not set a column's default.
@@ -518,6 +697,8 @@ module ActiveRecord
518
697
  #
519
698
  # CREATE INDEX by_name ON accounts(name(10))
520
699
  #
700
+ # ====== Creating an index with specific key lengths for multiple keys
701
+ #
521
702
  # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
522
703
  #
523
704
  # generates:
@@ -534,7 +715,7 @@ module ActiveRecord
534
715
  #
535
716
  # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
536
717
  #
537
- # Note: MySQL doesn't yet support index order (it accepts the syntax but ignores it).
718
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
538
719
  #
539
720
  # ====== Creating a partial index
540
721
  #
@@ -557,6 +738,19 @@ module ActiveRecord
557
738
  #
558
739
  # Note: only supported by PostgreSQL and MySQL
559
740
  #
741
+ # ====== Creating an index with a specific operator class
742
+ #
743
+ # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
744
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
745
+ #
746
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
747
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
748
+ #
749
+ # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
750
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
751
+ #
752
+ # Note: only supported by PostgreSQL
753
+ #
560
754
  # ====== Creating an index with a specific type
561
755
  #
562
756
  # add_index(:developers, :name, type: :fulltext)
@@ -565,7 +759,7 @@ module ActiveRecord
565
759
  #
566
760
  # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
567
761
  #
568
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
762
+ # Note: only supported by MySQL.
569
763
  def add_index(table_name, column_name, options = {})
570
764
  index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
571
765
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -573,15 +767,15 @@ module ActiveRecord
573
767
 
574
768
  # Removes the given index from the table.
575
769
  #
576
- # Removes the +index_accounts_on_column+ in the +accounts+ table.
770
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
577
771
  #
578
- # remove_index :accounts, :column
772
+ # remove_index :accounts, :branch_id
579
773
  #
580
- # Removes the index named +index_accounts_on_branch_id+ in the +accounts+ table.
774
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
581
775
  #
582
776
  # remove_index :accounts, column: :branch_id
583
777
  #
584
- # Removes the index named +index_accounts_on_branch_id_and_party_id+ in the +accounts+ table.
778
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
585
779
  #
586
780
  # remove_index :accounts, column: [:branch_id, :party_id]
587
781
  #
@@ -590,10 +784,7 @@ module ActiveRecord
590
784
  # remove_index :accounts, name: :by_branch_party
591
785
  #
592
786
  def remove_index(table_name, options = {})
593
- remove_index!(table_name, index_name_for_remove(table_name, options))
594
- end
595
-
596
- def remove_index!(table_name, index_name) #:nodoc:
787
+ index_name = index_name_for_remove(table_name, options)
597
788
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
598
789
  end
599
790
 
@@ -606,7 +797,7 @@ module ActiveRecord
606
797
  def rename_index(table_name, old_name, new_name)
607
798
  validate_index_length!(table_name, new_name)
608
799
 
609
- # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
800
+ # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
610
801
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
611
802
  return unless old_index_def
612
803
  add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
@@ -623,36 +814,35 @@ module ActiveRecord
623
814
  raise ArgumentError, "You must specify the index name"
624
815
  end
625
816
  else
626
- index_name(table_name, :column => options)
817
+ index_name(table_name, index_name_options(options))
627
818
  end
628
819
  end
629
820
 
630
821
  # Verifies the existence of an index with a given name.
631
- #
632
- # The default argument is returned if the underlying implementation does not define the indexes method,
633
- # as there's no way to determine the correct answer in that case.
634
- def index_name_exists?(table_name, index_name, default)
635
- return default unless respond_to?(:indexes)
822
+ def index_name_exists?(table_name, index_name)
636
823
  index_name = index_name.to_s
637
824
  indexes(table_name).detect { |i| i.name == index_name }
638
825
  end
639
826
 
640
- # Adds a reference. The reference column is an integer by default,
827
+ # Adds a reference. The reference column is a bigint by default,
641
828
  # the <tt>:type</tt> option can be used to specify a different type.
642
829
  # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
643
- # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
830
+ # #add_reference and #add_belongs_to are acceptable.
644
831
  #
645
832
  # The +options+ hash can include the following keys:
646
833
  # [<tt>:type</tt>]
647
- # The reference column type. Defaults to +:integer+.
834
+ # The reference column type. Defaults to +:bigint+.
648
835
  # [<tt>:index</tt>]
649
- # Add an appropriate index. Defaults to false.
836
+ # Add an appropriate index. Defaults to true.
837
+ # See #add_index for usage of this option.
650
838
  # [<tt>:foreign_key</tt>]
651
- # Add an appropriate foreign key. Defaults to false.
839
+ # Add an appropriate foreign key constraint. Defaults to false.
652
840
  # [<tt>:polymorphic</tt>]
653
- # Wether an additional +_type+ column should be added. Defaults to false.
841
+ # Whether an additional +_type+ column should be added. Defaults to false.
842
+ # [<tt>:null</tt>]
843
+ # Whether the column allows nulls. Defaults to true.
654
844
  #
655
- # ====== Create a user_id integer column
845
+ # ====== Create a user_id bigint column
656
846
  #
657
847
  # add_reference(:products, :user)
658
848
  #
@@ -664,28 +854,29 @@ module ActiveRecord
664
854
  #
665
855
  # add_reference(:products, :supplier, polymorphic: true, index: true)
666
856
  #
667
- def add_reference(table_name, ref_name, options = {})
668
- polymorphic = options.delete(:polymorphic)
669
- index_options = options.delete(:index)
670
- type = options.delete(:type) || :integer
671
- foreign_key_options = options.delete(:foreign_key)
672
-
673
- if polymorphic && foreign_key_options
674
- raise ArgumentError, "Cannot add a foreign key to a polymorphic relation"
675
- end
676
-
677
- add_column(table_name, "#{ref_name}_id", type, options)
678
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
679
- add_index(table_name, polymorphic ? %w[type id].map{ |t| "#{ref_name}_#{t}" } : "#{ref_name}_id", index_options.is_a?(Hash) ? index_options : {}) if index_options
680
- if foreign_key_options
681
- to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
682
- add_foreign_key(table_name, to_table, foreign_key_options.is_a?(Hash) ? foreign_key_options : {})
683
- end
857
+ # ====== Create a supplier_id column with a unique index
858
+ #
859
+ # add_reference(:products, :supplier, index: { unique: true })
860
+ #
861
+ # ====== Create a supplier_id column with a named index
862
+ #
863
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
864
+ #
865
+ # ====== Create a supplier_id column and appropriate foreign key
866
+ #
867
+ # add_reference(:products, :supplier, foreign_key: true)
868
+ #
869
+ # ====== Create a supplier_id column and a foreign key to the firms table
870
+ #
871
+ # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
872
+ #
873
+ def add_reference(table_name, ref_name, **options)
874
+ ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
684
875
  end
685
876
  alias :add_belongs_to :add_reference
686
877
 
687
878
  # Removes the reference(s). Also removes a +type+ column if one exists.
688
- # <tt>remove_reference</tt>, <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are acceptable.
879
+ # #remove_reference and #remove_belongs_to are acceptable.
689
880
  #
690
881
  # ====== Remove the reference
691
882
  #
@@ -699,19 +890,25 @@ module ActiveRecord
699
890
  #
700
891
  # remove_reference(:products, :user, index: true, foreign_key: true)
701
892
  #
702
- def remove_reference(table_name, ref_name, options = {})
703
- if options[:foreign_key]
704
- to_table = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
705
- remove_foreign_key(table_name, to_table)
893
+ def remove_reference(table_name, ref_name, foreign_key: false, polymorphic: false, **options)
894
+ if foreign_key
895
+ reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
896
+ if foreign_key.is_a?(Hash)
897
+ foreign_key_options = foreign_key
898
+ else
899
+ foreign_key_options = { to_table: reference_name }
900
+ end
901
+ foreign_key_options[:column] ||= "#{ref_name}_id"
902
+ remove_foreign_key(table_name, foreign_key_options)
706
903
  end
707
904
 
708
905
  remove_column(table_name, "#{ref_name}_id")
709
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
906
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
710
907
  end
711
908
  alias :remove_belongs_to :remove_reference
712
909
 
713
910
  # Returns an array of foreign keys for the given table.
714
- # The foreign keys are represented as +ForeignKeyDefinition+ objects.
911
+ # The foreign keys are represented as ForeignKeyDefinition objects.
715
912
  def foreign_keys(table_name)
716
913
  raise NotImplementedError, "foreign_keys is not implemented"
717
914
  end
@@ -729,11 +926,11 @@ module ActiveRecord
729
926
  #
730
927
  # generates:
731
928
  #
732
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
929
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
733
930
  #
734
931
  # ====== Creating a foreign key on a specific column
735
932
  #
736
- # add_foreign_key :articles, :users, column: :author_id, primary_key: :lng_id
933
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
737
934
  #
738
935
  # generates:
739
936
  #
@@ -745,7 +942,7 @@ module ActiveRecord
745
942
  #
746
943
  # generates:
747
944
  #
748
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
945
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
749
946
  #
750
947
  # The +options+ hash can include the following keys:
751
948
  # [<tt>:column</tt>]
@@ -755,28 +952,25 @@ module ActiveRecord
755
952
  # [<tt>:name</tt>]
756
953
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
757
954
  # [<tt>:on_delete</tt>]
758
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
955
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
759
956
  # [<tt>:on_update</tt>]
760
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade:+ and +:restrict+
957
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
958
+ # [<tt>:validate</tt>]
959
+ # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+.
761
960
  def add_foreign_key(from_table, to_table, options = {})
762
961
  return unless supports_foreign_keys?
763
962
 
764
- options[:column] ||= foreign_key_column_for(to_table)
765
-
766
- options = {
767
- column: options[:column],
768
- primary_key: options[:primary_key],
769
- name: foreign_key_name(from_table, options),
770
- on_delete: options[:on_delete],
771
- on_update: options[:on_update]
772
- }
963
+ options = foreign_key_options(from_table, to_table, options)
773
964
  at = create_alter_table from_table
774
965
  at.add_foreign_key to_table, options
775
966
 
776
967
  execute schema_creation.accept(at)
777
968
  end
778
969
 
779
- # Removes the given foreign key from the table.
970
+ # Removes the given foreign key from the table. Any option parameters provided
971
+ # will be used to re-add the foreign key in case of a migration rollback.
972
+ # It is recommended that you provide any options used when creating the foreign
973
+ # key so that the migration can be reverted properly.
780
974
  #
781
975
  # Removes the foreign key on +accounts.branch_id+.
782
976
  #
@@ -790,24 +984,11 @@ module ActiveRecord
790
984
  #
791
985
  # remove_foreign_key :accounts, name: :special_fk_name
792
986
  #
987
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
793
988
  def remove_foreign_key(from_table, options_or_to_table = {})
794
989
  return unless supports_foreign_keys?
795
990
 
796
- if options_or_to_table.is_a?(Hash)
797
- options = options_or_to_table
798
- else
799
- options = { column: foreign_key_column_for(options_or_to_table) }
800
- end
801
-
802
- fk_name_to_delete = options.fetch(:name) do
803
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
804
-
805
- if fk_to_delete
806
- fk_to_delete.name
807
- else
808
- raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
809
- end
810
- end
991
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
811
992
 
812
993
  at = create_alter_table from_table
813
994
  at.drop_foreign_key fk_name_to_delete
@@ -815,6 +996,21 @@ module ActiveRecord
815
996
  execute schema_creation.accept(at)
816
997
  end
817
998
 
999
+ # Checks to see if a foreign key exists on a table for a given foreign key definition.
1000
+ #
1001
+ # # Checks to see if a foreign key exists.
1002
+ # foreign_key_exists?(:accounts, :branches)
1003
+ #
1004
+ # # Checks to see if a foreign key on a specified column exists.
1005
+ # foreign_key_exists?(:accounts, column: :owner_id)
1006
+ #
1007
+ # # Checks to see if a foreign key with a custom name exists.
1008
+ # foreign_key_exists?(:accounts, name: "special_fk_name")
1009
+ #
1010
+ def foreign_key_exists?(from_table, options_or_to_table = {})
1011
+ foreign_key_for(from_table, options_or_to_table).present?
1012
+ end
1013
+
818
1014
  def foreign_key_column_for(table_name) # :nodoc:
819
1015
  prefix = Base.table_name_prefix
820
1016
  suffix = Base.table_name_suffix
@@ -822,47 +1018,54 @@ module ActiveRecord
822
1018
  "#{name.singularize}_id"
823
1019
  end
824
1020
 
825
- def dump_schema_information #:nodoc:
826
- sm_table = ActiveRecord::Migrator.schema_migrations_table_name
1021
+ def foreign_key_options(from_table, to_table, options) # :nodoc:
1022
+ options = options.dup
1023
+ options[:column] ||= foreign_key_column_for(to_table)
1024
+ options[:name] ||= foreign_key_name(from_table, options)
1025
+ options
1026
+ end
827
1027
 
828
- ActiveRecord::SchemaMigration.order('version').map { |sm|
829
- "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
830
- }.join "\n\n"
1028
+ def dump_schema_information #:nodoc:
1029
+ versions = ActiveRecord::SchemaMigration.all_versions
1030
+ insert_versions_sql(versions) if versions.any?
831
1031
  end
832
1032
 
833
- # Should not be called normally, but this operation is non-destructive.
834
- # The migrations module handles this automatically.
835
- def initialize_schema_migrations_table
836
- ActiveRecord::SchemaMigration.create_table
1033
+ def internal_string_options_for_primary_key # :nodoc:
1034
+ { primary_key: true }
837
1035
  end
838
1036
 
839
- def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
1037
+ def assume_migrated_upto_version(version, migrations_paths)
840
1038
  migrations_paths = Array(migrations_paths)
841
1039
  version = version.to_i
842
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1040
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
843
1041
 
844
- migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i)
845
- versions = ActiveRecord::Migrator.migration_files(migrations_paths).map do |file|
846
- ActiveRecord::Migrator.parse_migration_filename(file).first.to_i
1042
+ migrated = ActiveRecord::SchemaMigration.all_versions.map(&:to_i)
1043
+ versions = migration_context.migration_files.map do |file|
1044
+ migration_context.parse_migration_filename(file).first.to_i
847
1045
  end
848
1046
 
849
1047
  unless migrated.include?(version)
850
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
1048
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
851
1049
  end
852
1050
 
853
- inserted = Set.new
854
- (versions - migrated).each do |v|
855
- if inserted.include?(v)
856
- raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
857
- elsif v < version
858
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
859
- inserted << v
1051
+ inserting = (versions - migrated).select { |v| v < version }
1052
+ if inserting.any?
1053
+ if (duplicate = inserting.detect { |v| inserting.count(v) > 1 })
1054
+ raise "Duplicate migration #{duplicate}. Please renumber your migrations to resolve the conflict."
1055
+ end
1056
+ if supports_multi_insert?
1057
+ execute insert_versions_sql(inserting)
1058
+ else
1059
+ inserting.each do |v|
1060
+ execute insert_versions_sql(v)
1061
+ end
860
1062
  end
861
1063
  end
862
1064
  end
863
1065
 
864
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
865
- if native = native_database_types[type.to_sym]
1066
+ def type_to_sql(type, limit: nil, precision: nil, scale: nil, **) # :nodoc:
1067
+ type = type.to_sym if type
1068
+ if native = native_database_types[type]
866
1069
  column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
867
1070
 
868
1071
  if type == :decimal # ignore limit, use precision and scale
@@ -878,6 +1081,12 @@ module ActiveRecord
878
1081
  raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
879
1082
  end
880
1083
 
1084
+ elsif [:datetime, :timestamp, :time, :interval].include?(type) && precision ||= native[:precision]
1085
+ if (0..6) === precision
1086
+ column_type_sql << "(#{precision})"
1087
+ else
1088
+ raise(ActiveRecordError, "No #{native[:name]} type has precision of #{precision}. The allowed range of precision is from 0 to 6")
1089
+ end
881
1090
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
882
1091
  column_type_sql << "(#{limit})"
883
1092
  end
@@ -889,7 +1098,7 @@ module ActiveRecord
889
1098
  end
890
1099
 
891
1100
  # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
892
- # PostgreSQL, MySQL, and Oracle overrides this for custom DISTINCT syntax - they
1101
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
893
1102
  # require the order columns appear in the SELECT.
894
1103
  #
895
1104
  # columns_for_distinct("posts.id", ["posts.created_at desc"])
@@ -898,14 +1107,14 @@ module ActiveRecord
898
1107
  columns
899
1108
  end
900
1109
 
901
- include TimestampDefaultDeprecation
902
1110
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
903
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
1111
+ # Additional options (like +:null+) are forwarded to #add_column.
904
1112
  #
905
- # add_timestamps(:suppliers, null: false)
1113
+ # add_timestamps(:suppliers, null: true)
906
1114
  #
907
1115
  def add_timestamps(table_name, options = {})
908
- emit_warning_if_null_unspecified(:add_timestamps, options)
1116
+ options[:null] = false if options[:null].nil?
1117
+
909
1118
  add_column table_name, :created_at, :datetime, options
910
1119
  add_column table_name, :updated_at, :datetime, options
911
1120
  end
@@ -923,16 +1132,15 @@ module ActiveRecord
923
1132
  Table.new(table_name, base)
924
1133
  end
925
1134
 
926
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
927
- column_names = Array(column_name)
928
- index_name = index_name(table_name, column: column_names)
1135
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1136
+ column_names = index_column_names(column_name)
929
1137
 
930
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
1138
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
931
1139
 
932
- index_type = options[:unique] ? "UNIQUE" : ""
933
1140
  index_type = options[:type].to_s if options.key?(:type)
1141
+ index_type ||= options[:unique] ? "UNIQUE" : ""
934
1142
  index_name = options[:name].to_s if options.key?(:name)
935
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
1143
+ index_name ||= index_name(table_name, column_names)
936
1144
 
937
1145
  if options.key?(:algorithm)
938
1146
  algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -946,63 +1154,99 @@ module ActiveRecord
946
1154
  index_options = options[:where] ? " WHERE #{options[:where]}" : ""
947
1155
  end
948
1156
 
949
- if index_name.length > max_index_length
950
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
951
- end
952
- if table_exists?(table_name) && index_name_exists?(table_name, index_name, false)
1157
+ validate_index_length!(table_name, index_name, options.fetch(:internal, false))
1158
+
1159
+ if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
953
1160
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
954
1161
  end
955
1162
  index_columns = quoted_columns_for_index(column_names, options).join(", ")
956
1163
 
957
- [index_name, index_type, index_columns, index_options, algorithm, using]
1164
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
958
1165
  end
959
1166
 
960
- protected
961
- def add_index_sort_order(option_strings, column_names, options = {})
962
- if options.is_a?(Hash) && order = options[:order]
963
- case order
964
- when Hash
965
- column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
966
- when String
967
- column_names.each {|name| option_strings[name] += " #{order.upcase}"}
968
- end
969
- end
1167
+ def options_include_default?(options)
1168
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1169
+ end
1170
+
1171
+ # Changes the comment for a table or removes it if +nil+.
1172
+ def change_table_comment(table_name, comment)
1173
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
1174
+ end
1175
+
1176
+ # Changes the comment for a column or removes it if +nil+.
1177
+ def change_column_comment(table_name, column_name, comment)
1178
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
1179
+ end
970
1180
 
971
- return option_strings
1181
+ def create_schema_dumper(options) # :nodoc:
1182
+ SchemaDumper.create(self, options)
1183
+ end
1184
+
1185
+ private
1186
+ def column_options_keys
1187
+ [:limit, :precision, :scale, :default, :null, :collation, :comment]
972
1188
  end
973
1189
 
974
- # Overridden by the MySQL adapter for supporting index lengths
975
- def quoted_columns_for_index(column_names, options = {})
976
- option_strings = Hash[column_names.map {|name| [name, '']}]
1190
+ def add_index_sort_order(quoted_columns, **options)
1191
+ orders = options_for_index_columns(options[:order])
1192
+ quoted_columns.each do |name, column|
1193
+ column << " #{orders[name].upcase}" if orders[name].present?
1194
+ end
1195
+ end
977
1196
 
978
- # add index sort order if supported
1197
+ def options_for_index_columns(options)
1198
+ if options.is_a?(Hash)
1199
+ options.symbolize_keys
1200
+ else
1201
+ Hash.new { |hash, column| hash[column] = options }
1202
+ end
1203
+ end
1204
+
1205
+ # Overridden by the MySQL adapter for supporting index lengths and by
1206
+ # the PostgreSQL adapter for supporting operator classes.
1207
+ def add_options_for_index_columns(quoted_columns, **options)
979
1208
  if supports_index_sort_order?
980
- option_strings = add_index_sort_order(option_strings, column_names, options)
1209
+ quoted_columns = add_index_sort_order(quoted_columns, options)
981
1210
  end
982
1211
 
983
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
1212
+ quoted_columns
984
1213
  end
985
1214
 
986
- def options_include_default?(options)
987
- options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1215
+ def quoted_columns_for_index(column_names, **options)
1216
+ return [column_names] if column_names.is_a?(String)
1217
+
1218
+ quoted_columns = Hash[column_names.map { |name| [name.to_sym, quote_column_name(name).dup] }]
1219
+ add_options_for_index_columns(quoted_columns, options).values
988
1220
  end
989
1221
 
990
1222
  def index_name_for_remove(table_name, options = {})
991
- index_name = index_name(table_name, options)
1223
+ return options[:name] if can_remove_index_by_name?(options)
992
1224
 
993
- unless index_name_exists?(table_name, index_name, true)
994
- if options.is_a?(Hash) && options.has_key?(:name)
995
- options_without_column = options.dup
996
- options_without_column.delete :column
997
- index_name_without_column = index_name(table_name, options_without_column)
1225
+ checks = []
998
1226
 
999
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
1000
- end
1227
+ if options.is_a?(Hash)
1228
+ checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1229
+ column_names = index_column_names(options[:column])
1230
+ else
1231
+ column_names = index_column_names(options)
1232
+ end
1001
1233
 
1002
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' does not exist"
1234
+ if column_names.present?
1235
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1003
1236
  end
1004
1237
 
1005
- index_name
1238
+ raise ArgumentError, "No name or columns specified" if checks.none?
1239
+
1240
+ matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } }
1241
+
1242
+ if matching_indexes.count > 1
1243
+ raise ArgumentError, "Multiple indexes found on #{table_name} columns #{column_names}. " \
1244
+ "Specify an index name from #{matching_indexes.map(&:name).join(', ')}"
1245
+ elsif matching_indexes.none?
1246
+ raise ArgumentError, "No indexes found on #{table_name} with the options provided."
1247
+ else
1248
+ matching_indexes.first.name
1249
+ end
1006
1250
  end
1007
1251
 
1008
1252
  def rename_table_indexes(table_name, new_name)
@@ -1027,28 +1271,126 @@ module ActiveRecord
1027
1271
  end
1028
1272
  end
1029
1273
 
1030
- private
1031
- def create_table_definition(name, temporary, options, as = nil)
1032
- TableDefinition.new native_database_types, name, temporary, options, as
1033
- end
1274
+ def schema_creation
1275
+ SchemaCreation.new(self)
1276
+ end
1034
1277
 
1035
- def create_alter_table(name)
1036
- AlterTable.new create_table_definition(name, false, {})
1037
- end
1278
+ def create_table_definition(*args)
1279
+ TableDefinition.new(*args)
1280
+ end
1038
1281
 
1039
- def foreign_key_name(table_name, options) # :nodoc:
1040
- identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1041
- hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1042
- options.fetch(:name) do
1043
- "fk_rails_#{hashed_identifier}"
1282
+ def create_alter_table(name)
1283
+ AlterTable.new create_table_definition(name)
1044
1284
  end
1045
- end
1046
1285
 
1047
- def validate_index_length!(table_name, new_name)
1048
- if new_name.length > allowed_index_name_length
1049
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1286
+ def fetch_type_metadata(sql_type)
1287
+ cast_type = lookup_cast_type(sql_type)
1288
+ SqlTypeMetadata.new(
1289
+ sql_type: sql_type,
1290
+ type: cast_type.type,
1291
+ limit: cast_type.limit,
1292
+ precision: cast_type.precision,
1293
+ scale: cast_type.scale,
1294
+ )
1295
+ end
1296
+
1297
+ def index_column_names(column_names)
1298
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1299
+ column_names
1300
+ else
1301
+ Array(column_names)
1302
+ end
1303
+ end
1304
+
1305
+ def index_name_options(column_names)
1306
+ if column_names.is_a?(String) && /\W/.match?(column_names)
1307
+ column_names = column_names.scan(/\w+/).join("_")
1308
+ end
1309
+
1310
+ { column: column_names }
1311
+ end
1312
+
1313
+ def foreign_key_name(table_name, options)
1314
+ options.fetch(:name) do
1315
+ identifier = "#{table_name}_#{options.fetch(:column)}_fk"
1316
+ hashed_identifier = Digest::SHA256.hexdigest(identifier).first(10)
1317
+
1318
+ "fk_rails_#{hashed_identifier}"
1319
+ end
1320
+ end
1321
+
1322
+ def foreign_key_for(from_table, options_or_to_table = {})
1323
+ return unless supports_foreign_keys?
1324
+ foreign_keys(from_table).detect { |fk| fk.defined_for? options_or_to_table }
1325
+ end
1326
+
1327
+ def foreign_key_for!(from_table, options_or_to_table = {})
1328
+ foreign_key_for(from_table, options_or_to_table) || \
1329
+ raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{options_or_to_table}")
1330
+ end
1331
+
1332
+ def extract_foreign_key_action(specifier)
1333
+ case specifier
1334
+ when "CASCADE"; :cascade
1335
+ when "SET NULL"; :nullify
1336
+ when "RESTRICT"; :restrict
1337
+ end
1338
+ end
1339
+
1340
+ def validate_index_length!(table_name, new_name, internal = false)
1341
+ max_index_length = internal ? index_name_length : allowed_index_name_length
1342
+
1343
+ if new_name.length > max_index_length
1344
+ raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
1345
+ end
1346
+ end
1347
+
1348
+ def extract_new_default_value(default_or_changes)
1349
+ if default_or_changes.is_a?(Hash) && default_or_changes.has_key?(:from) && default_or_changes.has_key?(:to)
1350
+ default_or_changes[:to]
1351
+ else
1352
+ default_or_changes
1353
+ end
1354
+ end
1355
+
1356
+ def can_remove_index_by_name?(options)
1357
+ options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty?
1358
+ end
1359
+
1360
+ def add_column_for_alter(table_name, column_name, type, options = {})
1361
+ td = create_table_definition(table_name)
1362
+ cd = td.new_column_definition(column_name, type, options)
1363
+ schema_creation.accept(AddColumnDefinition.new(cd))
1364
+ end
1365
+
1366
+ def remove_column_for_alter(table_name, column_name, type = nil, options = {})
1367
+ "DROP COLUMN #{quote_column_name(column_name)}"
1368
+ end
1369
+
1370
+ def remove_columns_for_alter(table_name, *column_names)
1371
+ column_names.map { |column_name| remove_column_for_alter(table_name, column_name) }
1372
+ end
1373
+
1374
+ def insert_versions_sql(versions)
1375
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1376
+
1377
+ if versions.is_a?(Array)
1378
+ sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup
1379
+ sql << versions.map { |v| "(#{quote(v)})" }.join(",\n")
1380
+ sql << ";\n\n"
1381
+ sql
1382
+ else
1383
+ "INSERT INTO #{sm_table} (version) VALUES (#{quote(versions)});"
1384
+ end
1385
+ end
1386
+
1387
+ def data_source_sql(name = nil, type: nil)
1388
+ raise NotImplementedError
1389
+ end
1390
+
1391
+ def quoted_scope(name = nil, type: nil)
1392
+ raise NotImplementedError
1050
1393
  end
1051
- end
1052
1394
  end
1053
1395
  end
1054
1396
  end