activerecord 4.2.0 → 5.2.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (274) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +640 -928
  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 +264 -247
  8. data/lib/active_record/association_relation.rb +24 -6
  9. data/lib/active_record/associations/alias_tracker.rb +29 -35
  10. data/lib/active_record/associations/association.rb +87 -41
  11. data/lib/active_record/associations/association_scope.rb +106 -132
  12. data/lib/active_record/associations/belongs_to_association.rb +55 -36
  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 +14 -23
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +50 -39
  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 +145 -266
  22. data/lib/active_record/associations/collection_proxy.rb +242 -138
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +35 -75
  25. data/lib/active_record/associations/has_many_through_association.rb +51 -69
  26. data/lib/active_record/associations/has_one_association.rb +39 -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 -81
  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 +134 -154
  32. data/lib/active_record/associations/preloader/association.rb +85 -116
  33. data/lib/active_record/associations/preloader/through_association.rb +85 -74
  34. data/lib/active_record/associations/preloader.rb +83 -93
  35. data/lib/active_record/associations/singular_association.rb +27 -40
  36. data/lib/active_record/associations/through_association.rb +48 -23
  37. data/lib/active_record/associations.rb +1732 -1596
  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 +12 -5
  41. data/lib/active_record/attribute_methods/dirty.rb +94 -125
  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 +62 -36
  47. data/lib/active_record/attribute_methods/write.rb +31 -46
  48. data/lib/active_record/attribute_methods.rb +170 -117
  49. data/lib/active_record/attributes.rb +201 -74
  50. data/lib/active_record/autosave_association.rb +118 -45
  51. data/lib/active_record/base.rb +60 -48
  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 +37 -13
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +712 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +10 -5
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +254 -87
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +72 -22
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -52
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +6 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +67 -46
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +328 -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 +617 -212
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +139 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +332 -191
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +567 -563
  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 +42 -195
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +35 -11
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -115
  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 -57
  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 +5 -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 -13
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +3 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +7 -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 +7 -9
  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 -1
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +65 -51
  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 +466 -280
  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 +439 -330
  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 -324
  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 +205 -202
  129. data/lib/active_record/counter_cache.rb +80 -37
  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 +136 -90
  133. data/lib/active_record/errors.rb +180 -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 +11 -6
  137. data/lib/active_record/fixture_set/file.rb +35 -9
  138. data/lib/active_record/fixtures.rb +193 -135
  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 +48 -0
  144. data/lib/active_record/locale/en.yml +3 -2
  145. data/lib/active_record/locking/optimistic.rb +92 -98
  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 +594 -267
  152. data/lib/active_record/model_schema.rb +292 -111
  153. data/lib/active_record/nested_attributes.rb +266 -214
  154. data/lib/active_record/no_touching.rb +8 -2
  155. data/lib/active_record/null_relation.rb +24 -37
  156. data/lib/active_record/persistence.rb +350 -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 +117 -35
  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 +160 -174
  163. data/lib/active_record/readonly_attributes.rb +5 -4
  164. data/lib/active_record/reflection.rb +447 -288
  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 +259 -244
  168. data/lib/active_record/relation/delegation.rb +67 -60
  169. data/lib/active_record/relation/finder_methods.rb +290 -253
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +91 -68
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +24 -23
  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 +118 -92
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +446 -389
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +18 -16
  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 -339
  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 -19
  193. data/lib/active_record/scoping/default.rb +102 -84
  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 +136 -95
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +59 -89
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +84 -31
  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 +208 -123
  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 -41
  212. data/lib/active_record/type/date_time.rb +4 -38
  213. data/lib/active_record/type/decimal_without_scale.rb +6 -2
  214. data/lib/active_record/type/hash_lookup_type_map.rb +13 -5
  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 +30 -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 +41 -32
  231. data/lib/active_record/validations.rb +38 -35
  232. data/lib/active_record/version.rb +3 -1
  233. data/lib/active_record.rb +36 -21
  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 -6
  238. data/lib/rails/generators/active_record/migration/templates/{migration.rb → migration.rb.tt} +8 -7
  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.rb +7 -5
  243. metadata +77 -53
  244. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  245. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  246. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  247. data/lib/active_record/associations/preloader/has_many_through.rb +0 -19
  248. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  249. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  250. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  251. data/lib/active_record/attribute.rb +0 -149
  252. data/lib/active_record/attribute_set/builder.rb +0 -86
  253. data/lib/active_record/attribute_set.rb +0 -77
  254. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  255. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  256. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  257. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  258. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +0 -35
  259. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  260. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  261. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  262. data/lib/active_record/type/big_integer.rb +0 -13
  263. data/lib/active_record/type/binary.rb +0 -50
  264. data/lib/active_record/type/boolean.rb +0 -30
  265. data/lib/active_record/type/decimal.rb +0 -40
  266. data/lib/active_record/type/decorator.rb +0 -14
  267. data/lib/active_record/type/float.rb +0 -19
  268. data/lib/active_record/type/integer.rb +0 -55
  269. data/lib/active_record/type/mutable.rb +0 -16
  270. data/lib/active_record/type/numeric.rb +0 -36
  271. data/lib/active_record/type/string.rb +0 -36
  272. data/lib/active_record/type/time_value.rb +0 -38
  273. data/lib/active_record/type/value.rb +0 -101
  274. /data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +0 -0
@@ -1,4 +1,8 @@
1
- require 'active_record/migration/join_table'
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"
2
6
 
3
7
  module ActiveRecord
4
8
  module ConnectionAdapters # :nodoc:
@@ -12,9 +16,41 @@ module ActiveRecord
12
16
  {}
13
17
  end
14
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
+
15
28
  # Truncates a table alias according to the limits of the current adapter.
16
29
  def table_alias_for(table_name)
17
- table_name[0...table_alias_length].tr('.', '_')
30
+ table_name[0...table_alias_length].tr(".", "_")
31
+ end
32
+
33
+ # Returns the relation names useable to back Active Record models.
34
+ # For most adapters this means all #tables and #views.
35
+ def data_sources
36
+ query_values(data_source_sql, "SCHEMA")
37
+ rescue NotImplementedError
38
+ tables | views
39
+ end
40
+
41
+ # Checks to see if the data source +name+ exists on the database.
42
+ #
43
+ # data_source_exists?(:ebooks)
44
+ #
45
+ def data_source_exists?(name)
46
+ query_values(data_source_sql(name), "SCHEMA").any? if name.present?
47
+ rescue NotImplementedError
48
+ data_sources.include?(name.to_s)
49
+ end
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")
18
54
  end
19
55
 
20
56
  # Checks to see if the table +table_name+ exists on the database.
@@ -22,11 +58,30 @@ module ActiveRecord
22
58
  # table_exists?(:developers)
23
59
  #
24
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
25
63
  tables.include?(table_name.to_s)
26
64
  end
27
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
+
28
81
  # Returns an array of indexes for the given table.
29
- # def indexes(table_name, name = nil) end
82
+ def indexes(table_name)
83
+ raise NotImplementedError, "#indexes is not implemented"
84
+ end
30
85
 
31
86
  # Checks to see if an index exists on a table for a given index definition.
32
87
  #
@@ -44,18 +99,21 @@ module ActiveRecord
44
99
  #
45
100
  def index_exists?(table_name, column_name, options = {})
46
101
  column_names = Array(column_name).map(&:to_s)
47
- index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names)
48
102
  checks = []
49
- checks << lambda { |i| i.name == index_name }
50
- checks << lambda { |i| i.columns == column_names }
103
+ checks << lambda { |i| Array(i.columns) == column_names }
51
104
  checks << lambda { |i| i.unique } if options[:unique]
105
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
52
106
 
53
107
  indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
54
108
  end
55
109
 
56
- # Returns an array of Column objects for the table specified by +table_name+.
57
- # See the concrete implementation for details on the expected parameter values.
58
- 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
59
117
 
60
118
  # Checks to see if a column exists in a given table.
61
119
  #
@@ -73,19 +131,27 @@ module ActiveRecord
73
131
  #
74
132
  def column_exists?(table_name, column_name, type = nil, options = {})
75
133
  column_name = column_name.to_s
76
- columns(table_name).any?{ |c| c.name == column_name &&
77
- (!type || c.type == type) &&
78
- (!options.key?(:limit) || c.limit == options[:limit]) &&
79
- (!options.key?(:precision) || c.precision == options[:precision]) &&
80
- (!options.key?(:scale) || c.scale == options[:scale]) &&
81
- (!options.key?(:default) || c.default == options[:default]) &&
82
- (!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
83
149
  end
84
150
 
85
151
  # Creates a new table with the name +table_name+. +table_name+ may either
86
152
  # be a String or a Symbol.
87
153
  #
88
- # 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
89
155
  # form or the regular form, like this:
90
156
  #
91
157
  # === Block form
@@ -117,13 +183,18 @@ module ActiveRecord
117
183
  # The +options+ hash can include the following keys:
118
184
  # [<tt>:id</tt>]
119
185
  # Whether to automatically add a primary key column. Defaults to true.
120
- # 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.
121
189
  # [<tt>:primary_key</tt>]
122
190
  # The name of the primary key, if one is to be added automatically.
123
- # 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.
124
194
  #
125
195
  # Note that Active Record models will automatically detect their
126
- # 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
127
198
  # to define the key explicitly.
128
199
  #
129
200
  # [<tt>:options</tt>]
@@ -145,7 +216,7 @@ module ActiveRecord
145
216
  # generates:
146
217
  #
147
218
  # CREATE TABLE suppliers (
148
- # id int(11) DEFAULT NULL auto_increment PRIMARY KEY
219
+ # id bigint auto_increment PRIMARY KEY
149
220
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
150
221
  #
151
222
  # ====== Rename the primary key column
@@ -157,22 +228,52 @@ module ActiveRecord
157
228
  # generates:
158
229
  #
159
230
  # CREATE TABLE objects (
160
- # guid int(11) DEFAULT NULL auto_increment PRIMARY KEY,
231
+ # guid bigint auto_increment PRIMARY KEY,
161
232
  # name varchar(80)
162
233
  # )
163
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
+ #
164
265
  # ====== Do not add a primary key column
165
266
  #
166
267
  # create_table(:categories_suppliers, id: false) do |t|
167
- # t.column :category_id, :integer
168
- # t.column :supplier_id, :integer
268
+ # t.column :category_id, :bigint
269
+ # t.column :supplier_id, :bigint
169
270
  # end
170
271
  #
171
272
  # generates:
172
273
  #
173
274
  # CREATE TABLE categories_suppliers (
174
- # category_id int,
175
- # supplier_id int
275
+ # category_id bigint,
276
+ # supplier_id bigint
176
277
  # )
177
278
  #
178
279
  # ====== Create a temporary table based on a query
@@ -186,25 +287,43 @@ module ActiveRecord
186
287
  # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
187
288
  #
188
289
  # See also TableDefinition#column for details on how to create columns.
189
- def create_table(table_name, options = {})
190
- 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
191
292
 
192
293
  if options[:id] != false && !options[:as]
193
294
  pk = options.fetch(:primary_key) do
194
295
  Base.get_primary_key table_name.to_s.singularize
195
296
  end
196
297
 
197
- 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
198
303
  end
199
304
 
200
305
  yield td if block_given?
201
306
 
202
- if options[:force] && table_exists?(table_name)
203
- drop_table(table_name, options)
307
+ if options[:force]
308
+ drop_table(table_name, options.merge(if_exists: true))
204
309
  end
205
310
 
206
311
  result = execute schema_creation.accept td
207
- td.indexes.each_pair { |c, o| add_index(table_name, c, o) } unless supports_indexes_in_create?
312
+
313
+ unless supports_indexes_in_create?
314
+ td.indexes.each do |column_name, index_options|
315
+ add_index(table_name, column_name, index_options)
316
+ end
317
+ end
318
+
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
325
+ end
326
+
208
327
  result
209
328
  end
210
329
 
@@ -214,9 +333,9 @@ module ActiveRecord
214
333
  # # Creates a table called 'assemblies_parts' with no id.
215
334
  # create_join_table(:assemblies, :parts)
216
335
  #
217
- # You can pass a +options+ hash can include the following keys:
336
+ # You can pass an +options+ hash which can include the following keys:
218
337
  # [<tt>:table_name</tt>]
219
- # Sets the table name overriding the default
338
+ # Sets the table name, overriding the default.
220
339
  # [<tt>:column_options</tt>]
221
340
  # Any extra options you want appended to the columns definition.
222
341
  # [<tt>:options</tt>]
@@ -227,7 +346,7 @@ module ActiveRecord
227
346
  # Set to true to drop the table before creating it.
228
347
  # Defaults to false.
229
348
  #
230
- # 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
231
350
  # its block form to do so yourself:
232
351
  #
233
352
  # create_join_table :products, :categories do |t|
@@ -242,31 +361,30 @@ module ActiveRecord
242
361
  # generates:
243
362
  #
244
363
  # CREATE TABLE assemblies_parts (
245
- # assembly_id int NOT NULL,
246
- # part_id int NOT NULL,
364
+ # assembly_id bigint NOT NULL,
365
+ # part_id bigint NOT NULL,
247
366
  # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
248
367
  #
249
- def create_join_table(table_1, table_2, options = {})
368
+ def create_join_table(table_1, table_2, column_options: {}, **options)
250
369
  join_table_name = find_join_table_name(table_1, table_2, options)
251
370
 
252
- column_options = options.delete(:column_options) || {}
253
- column_options.reverse_merge!(null: false)
371
+ column_options.reverse_merge!(null: false, index: false)
254
372
 
255
- 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 }
256
374
 
257
375
  create_table(join_table_name, options.merge!(id: false)) do |td|
258
- td.integer t1_column, column_options
259
- td.integer t2_column, column_options
376
+ td.references t1_ref, column_options
377
+ td.references t2_ref, column_options
260
378
  yield td if block_given?
261
379
  end
262
380
  end
263
381
 
264
382
  # Drops the join table specified by the given arguments.
265
- # See +create_join_table+ for details.
383
+ # See #create_join_table for details.
266
384
  #
267
385
  # Although this command ignores the block if one is given, it can be helpful
268
386
  # to provide one in a migration's +change+ method so it can be reverted.
269
- # 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.
270
388
  def drop_join_table(table_1, table_2, options = {})
271
389
  join_table_name = find_join_table_name(table_1, table_2, options)
272
390
  drop_table(join_table_name)
@@ -284,10 +402,12 @@ module ActiveRecord
284
402
  # [<tt>:bulk</tt>]
285
403
  # Set this to true to make this a bulk alter query, such as
286
404
  #
287
- # ALTER TABLE `users` ADD COLUMN age INT(11), ADD COLUMN birthdate DATETIME ...
405
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
288
406
  #
289
407
  # Defaults to false.
290
408
  #
409
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
410
+ #
291
411
  # ====== Add a column
292
412
  #
293
413
  # change_table(:suppliers) do |t|
@@ -312,7 +432,7 @@ module ActiveRecord
312
432
  # t.references :company
313
433
  # end
314
434
  #
315
- # Creates a <tt>company_id(integer)</tt> column.
435
+ # Creates a <tt>company_id(bigint)</tt> column.
316
436
  #
317
437
  # ====== Add a polymorphic foreign key column
318
438
  #
@@ -320,7 +440,7 @@ module ActiveRecord
320
440
  # t.belongs_to :company, polymorphic: true
321
441
  # end
322
442
  #
323
- # 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.
324
444
  #
325
445
  # ====== Remove a column
326
446
  #
@@ -341,7 +461,7 @@ module ActiveRecord
341
461
  # t.remove_index :company_id
342
462
  # end
343
463
  #
344
- # 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.
345
465
  def change_table(table_name, options = {})
346
466
  if supports_bulk_alter? && options[:bulk]
347
467
  recorder = ActiveRecord::Migration::CommandRecorder.new(self)
@@ -365,16 +485,96 @@ module ActiveRecord
365
485
  # [<tt>:force</tt>]
366
486
  # Set to +:cascade+ to drop dependent objects as well.
367
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.
368
491
  #
369
492
  # Although this command ignores most +options+ and the block if one is given,
370
493
  # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
371
- # 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.
372
495
  def drop_table(table_name, options = {})
373
- execute "DROP TABLE #{quote_table_name(table_name)}"
496
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
374
497
  end
375
498
 
376
- # Adds a new column to the named table.
377
- # 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
378
578
  def add_column(table_name, column_name, type, options = {})
379
579
  at = create_alter_table table_name
380
580
  at.add_column(column_name, type, options)
@@ -398,9 +598,9 @@ module ActiveRecord
398
598
  #
399
599
  # The +type+ and +options+ parameters will be ignored if present. It can be helpful
400
600
  # to provide these in a migration's +change+ method so it can be reverted.
401
- # 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.
402
602
  def remove_column(table_name, column_name, type = nil, options = {})
403
- 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)}"
404
604
  end
405
605
 
406
606
  # Changes the column's definition according to the new options.
@@ -422,11 +622,16 @@ module ActiveRecord
422
622
  #
423
623
  # change_column_default(:users, :email, nil)
424
624
  #
425
- 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)
426
631
  raise NotImplementedError, "change_column_default is not implemented"
427
632
  end
428
633
 
429
- # 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
430
635
  # indicates whether the value can be +NULL+. For example
431
636
  #
432
637
  # change_column_null(:users, :nickname, false)
@@ -438,7 +643,7 @@ module ActiveRecord
438
643
  # allows them to be +NULL+ (drops the constraint).
439
644
  #
440
645
  # The method accepts an optional fourth argument to replace existing
441
- # +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
442
647
  # constraint if needed, since otherwise those rows would not be valid.
443
648
  #
444
649
  # Please note the fourth argument does not set a column's default.
@@ -492,6 +697,8 @@ module ActiveRecord
492
697
  #
493
698
  # CREATE INDEX by_name ON accounts(name(10))
494
699
  #
700
+ # ====== Creating an index with specific key lengths for multiple keys
701
+ #
495
702
  # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
496
703
  #
497
704
  # generates:
@@ -508,7 +715,7 @@ module ActiveRecord
508
715
  #
509
716
  # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
510
717
  #
511
- # 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).
512
719
  #
513
720
  # ====== Creating a partial index
514
721
  #
@@ -518,6 +725,8 @@ module ActiveRecord
518
725
  #
519
726
  # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
520
727
  #
728
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
729
+ #
521
730
  # ====== Creating an index with a specific method
522
731
  #
523
732
  # add_index(:developers, :name, using: 'btree')
@@ -529,6 +738,19 @@ module ActiveRecord
529
738
  #
530
739
  # Note: only supported by PostgreSQL and MySQL
531
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
+ #
532
754
  # ====== Creating an index with a specific type
533
755
  #
534
756
  # add_index(:developers, :name, type: :fulltext)
@@ -537,7 +759,7 @@ module ActiveRecord
537
759
  #
538
760
  # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
539
761
  #
540
- # Note: only supported by MySQL. Supported: <tt>:fulltext</tt> and <tt>:spatial</tt> on MyISAM tables.
762
+ # Note: only supported by MySQL.
541
763
  def add_index(table_name, column_name, options = {})
542
764
  index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
543
765
  execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
@@ -545,15 +767,15 @@ module ActiveRecord
545
767
 
546
768
  # Removes the given index from the table.
547
769
  #
548
- # 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.
549
771
  #
550
- # remove_index :accounts, :column
772
+ # remove_index :accounts, :branch_id
551
773
  #
552
- # 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.
553
775
  #
554
776
  # remove_index :accounts, column: :branch_id
555
777
  #
556
- # 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.
557
779
  #
558
780
  # remove_index :accounts, column: [:branch_id, :party_id]
559
781
  #
@@ -562,10 +784,7 @@ module ActiveRecord
562
784
  # remove_index :accounts, name: :by_branch_party
563
785
  #
564
786
  def remove_index(table_name, options = {})
565
- remove_index!(table_name, index_name_for_remove(table_name, options))
566
- end
567
-
568
- def remove_index!(table_name, index_name) #:nodoc:
787
+ index_name = index_name_for_remove(table_name, options)
569
788
  execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
570
789
  end
571
790
 
@@ -576,10 +795,9 @@ module ActiveRecord
576
795
  # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
577
796
  #
578
797
  def rename_index(table_name, old_name, new_name)
579
- if new_name.length > allowed_index_name_length
580
- raise ArgumentError, "Index name '#{new_name}' on table '#{table_name}' is too long; the limit is #{allowed_index_name_length} characters"
581
- end
582
- # this is a naive implementation; some DBs may support this more efficiently (Postgres, for instance)
798
+ validate_index_length!(table_name, new_name)
799
+
800
+ # this is a naive implementation; some DBs may support this more efficiently (PostgreSQL, for instance)
583
801
  old_index_def = indexes(table_name).detect { |i| i.name == old_name }
584
802
  return unless old_index_def
585
803
  add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
@@ -596,26 +814,35 @@ module ActiveRecord
596
814
  raise ArgumentError, "You must specify the index name"
597
815
  end
598
816
  else
599
- index_name(table_name, :column => options)
817
+ index_name(table_name, index_name_options(options))
600
818
  end
601
819
  end
602
820
 
603
821
  # Verifies the existence of an index with a given name.
604
- #
605
- # The default argument is returned if the underlying implementation does not define the indexes method,
606
- # as there's no way to determine the correct answer in that case.
607
- def index_name_exists?(table_name, index_name, default)
608
- return default unless respond_to?(:indexes)
822
+ def index_name_exists?(table_name, index_name)
609
823
  index_name = index_name.to_s
610
824
  indexes(table_name).detect { |i| i.name == index_name }
611
825
  end
612
826
 
613
- # Adds a reference. Optionally adds a +type+ column, if <tt>:polymorphic</tt> option is provided.
614
- # The reference column is an +integer+ by default, the <tt>:type</tt> option can be used to specify
615
- # a different type.
616
- # <tt>add_reference</tt> and <tt>add_belongs_to</tt> are acceptable.
827
+ # Adds a reference. The reference column is a bigint by default,
828
+ # the <tt>:type</tt> option can be used to specify a different type.
829
+ # Optionally adds a +_type+ column, if <tt>:polymorphic</tt> option is provided.
830
+ # #add_reference and #add_belongs_to are acceptable.
617
831
  #
618
- # ====== Create a user_id integer column
832
+ # The +options+ hash can include the following keys:
833
+ # [<tt>:type</tt>]
834
+ # The reference column type. Defaults to +:bigint+.
835
+ # [<tt>:index</tt>]
836
+ # Add an appropriate index. Defaults to true.
837
+ # See #add_index for usage of this option.
838
+ # [<tt>:foreign_key</tt>]
839
+ # Add an appropriate foreign key constraint. Defaults to false.
840
+ # [<tt>:polymorphic</tt>]
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.
844
+ #
845
+ # ====== Create a user_id bigint column
619
846
  #
620
847
  # add_reference(:products, :user)
621
848
  #
@@ -623,26 +850,33 @@ module ActiveRecord
623
850
  #
624
851
  # add_reference(:products, :user, type: :string)
625
852
  #
626
- # ====== Create a supplier_id and supplier_type columns
853
+ # ====== Create supplier_id, supplier_type columns and appropriate index
627
854
  #
628
- # add_belongs_to(:products, :supplier, polymorphic: true)
855
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
629
856
  #
630
- # ====== Create a supplier_id, supplier_type columns and appropriate index
857
+ # ====== Create a supplier_id column with a unique index
631
858
  #
632
- # add_reference(:products, :supplier, polymorphic: true, index: true)
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)
633
868
  #
634
- def add_reference(table_name, ref_name, options = {})
635
- polymorphic = options.delete(:polymorphic)
636
- index_options = options.delete(:index)
637
- type = options.delete(:type) || :integer
638
- add_column(table_name, "#{ref_name}_id", type, options)
639
- add_column(table_name, "#{ref_name}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) if polymorphic
640
- 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
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))
641
875
  end
642
876
  alias :add_belongs_to :add_reference
643
877
 
644
878
  # Removes the reference(s). Also removes a +type+ column if one exists.
645
- # <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.
646
880
  #
647
881
  # ====== Remove the reference
648
882
  #
@@ -652,14 +886,29 @@ module ActiveRecord
652
886
  #
653
887
  # remove_reference(:products, :supplier, polymorphic: true)
654
888
  #
655
- def remove_reference(table_name, ref_name, options = {})
889
+ # ====== Remove the reference with a foreign key
890
+ #
891
+ # remove_reference(:products, :user, index: true, foreign_key: true)
892
+ #
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)
903
+ end
904
+
656
905
  remove_column(table_name, "#{ref_name}_id")
657
- remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
906
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
658
907
  end
659
908
  alias :remove_belongs_to :remove_reference
660
909
 
661
910
  # Returns an array of foreign keys for the given table.
662
- # The foreign keys are represented as +ForeignKeyDefinition+ objects.
911
+ # The foreign keys are represented as ForeignKeyDefinition objects.
663
912
  def foreign_keys(table_name)
664
913
  raise NotImplementedError, "foreign_keys is not implemented"
665
914
  end
@@ -668,8 +917,8 @@ module ActiveRecord
668
917
  # +to_table+ contains the referenced primary key.
669
918
  #
670
919
  # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
671
- # +identifier+ is a 10 character long random string. A custom name can be specified with
672
- # the <tt>:name</tt> option.
920
+ # +identifier+ is a 10 character long string which is deterministically generated from the
921
+ # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
673
922
  #
674
923
  # ====== Creating a simple foreign key
675
924
  #
@@ -677,7 +926,7 @@ module ActiveRecord
677
926
  #
678
927
  # generates:
679
928
  #
680
- # 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")
681
930
  #
682
931
  # ====== Creating a foreign key on a specific column
683
932
  #
@@ -693,7 +942,7 @@ module ActiveRecord
693
942
  #
694
943
  # generates:
695
944
  #
696
- # 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
697
946
  #
698
947
  # The +options+ hash can include the following keys:
699
948
  # [<tt>:column</tt>]
@@ -703,28 +952,25 @@ module ActiveRecord
703
952
  # [<tt>:name</tt>]
704
953
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
705
954
  # [<tt>:on_delete</tt>]
706
- # 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+
707
956
  # [<tt>:on_update</tt>]
708
- # 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+.
709
960
  def add_foreign_key(from_table, to_table, options = {})
710
961
  return unless supports_foreign_keys?
711
962
 
712
- options[:column] ||= foreign_key_column_for(to_table)
713
-
714
- options = {
715
- column: options[:column],
716
- primary_key: options[:primary_key],
717
- name: foreign_key_name(from_table, options),
718
- on_delete: options[:on_delete],
719
- on_update: options[:on_update]
720
- }
963
+ options = foreign_key_options(from_table, to_table, options)
721
964
  at = create_alter_table from_table
722
965
  at.add_foreign_key to_table, options
723
966
 
724
967
  execute schema_creation.accept(at)
725
968
  end
726
969
 
727
- # 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.
728
974
  #
729
975
  # Removes the foreign key on +accounts.branch_id+.
730
976
  #
@@ -738,24 +984,11 @@ module ActiveRecord
738
984
  #
739
985
  # remove_foreign_key :accounts, name: :special_fk_name
740
986
  #
987
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
741
988
  def remove_foreign_key(from_table, options_or_to_table = {})
742
989
  return unless supports_foreign_keys?
743
990
 
744
- if options_or_to_table.is_a?(Hash)
745
- options = options_or_to_table
746
- else
747
- options = { column: foreign_key_column_for(options_or_to_table) }
748
- end
749
-
750
- fk_name_to_delete = options.fetch(:name) do
751
- fk_to_delete = foreign_keys(from_table).detect {|fk| fk.column == options[:column].to_s }
752
-
753
- if fk_to_delete
754
- fk_to_delete.name
755
- else
756
- raise ArgumentError, "Table '#{from_table}' has no foreign key on column '#{options[:column]}'"
757
- end
758
- end
991
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
759
992
 
760
993
  at = create_alter_table from_table
761
994
  at.drop_foreign_key fk_name_to_delete
@@ -763,52 +996,76 @@ module ActiveRecord
763
996
  execute schema_creation.accept(at)
764
997
  end
765
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
+
766
1014
  def foreign_key_column_for(table_name) # :nodoc:
767
- "#{table_name.to_s.singularize}_id"
1015
+ prefix = Base.table_name_prefix
1016
+ suffix = Base.table_name_suffix
1017
+ name = table_name.to_s =~ /#{prefix}(.+)#{suffix}/ ? $1 : table_name.to_s
1018
+ "#{name.singularize}_id"
768
1019
  end
769
1020
 
770
- def dump_schema_information #:nodoc:
771
- 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
772
1027
 
773
- ActiveRecord::SchemaMigration.order('version').map { |sm|
774
- "INSERT INTO #{sm_table} (version) VALUES ('#{sm.version}');"
775
- }.join "\n\n"
1028
+ def dump_schema_information #:nodoc:
1029
+ versions = ActiveRecord::SchemaMigration.all_versions
1030
+ insert_versions_sql(versions) if versions.any?
776
1031
  end
777
1032
 
778
- # Should not be called normally, but this operation is non-destructive.
779
- # The migrations module handles this automatically.
780
- def initialize_schema_migrations_table
781
- ActiveRecord::SchemaMigration.create_table
1033
+ def internal_string_options_for_primary_key # :nodoc:
1034
+ { primary_key: true }
782
1035
  end
783
1036
 
784
- def assume_migrated_upto_version(version, migrations_paths = ActiveRecord::Migrator.migrations_paths)
1037
+ def assume_migrated_upto_version(version, migrations_paths)
785
1038
  migrations_paths = Array(migrations_paths)
786
1039
  version = version.to_i
787
- sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name)
1040
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
788
1041
 
789
- migrated = select_values("SELECT version FROM #{sm_table}").map { |v| v.to_i }
790
- paths = migrations_paths.map {|p| "#{p}/[0-9]*_*.rb" }
791
- versions = Dir[*paths].map do |filename|
792
- filename.split('/').last.split('_').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
793
1045
  end
794
1046
 
795
1047
  unless migrated.include?(version)
796
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')"
1048
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
797
1049
  end
798
1050
 
799
- inserted = Set.new
800
- (versions - migrated).each do |v|
801
- if inserted.include?(v)
802
- raise "Duplicate migration #{v}. Please renumber your migrations to resolve the conflict."
803
- elsif v < version
804
- execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')"
805
- 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
806
1062
  end
807
1063
  end
808
1064
  end
809
1065
 
810
- def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
811
- 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]
812
1069
  column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
813
1070
 
814
1071
  if type == :decimal # ignore limit, use precision and scale
@@ -824,6 +1081,12 @@ module ActiveRecord
824
1081
  raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
825
1082
  end
826
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
827
1090
  elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
828
1091
  column_type_sql << "(#{limit})"
829
1092
  end
@@ -835,22 +1098,23 @@ module ActiveRecord
835
1098
  end
836
1099
 
837
1100
  # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
838
- # Both PostgreSQL and Oracle overrides this for custom DISTINCT syntax - they
1101
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
839
1102
  # require the order columns appear in the SELECT.
840
1103
  #
841
1104
  # columns_for_distinct("posts.id", ["posts.created_at desc"])
842
- def columns_for_distinct(columns, orders) #:nodoc:
1105
+ #
1106
+ def columns_for_distinct(columns, orders) # :nodoc:
843
1107
  columns
844
1108
  end
845
1109
 
846
- include TimestampDefaultDeprecation
847
1110
  # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
848
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
1111
+ # Additional options (like +:null+) are forwarded to #add_column.
849
1112
  #
850
- # add_timestamps(:suppliers, null: false)
1113
+ # add_timestamps(:suppliers, null: true)
851
1114
  #
852
1115
  def add_timestamps(table_name, options = {})
853
- emit_warning_if_null_unspecified(options)
1116
+ options[:null] = false if options[:null].nil?
1117
+
854
1118
  add_column table_name, :created_at, :datetime, options
855
1119
  add_column table_name, :updated_at, :datetime, options
856
1120
  end
@@ -868,16 +1132,15 @@ module ActiveRecord
868
1132
  Table.new(table_name, base)
869
1133
  end
870
1134
 
871
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
872
- column_names = Array(column_name)
873
- 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)
874
1137
 
875
- 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)
876
1139
 
877
- index_type = options[:unique] ? "UNIQUE" : ""
878
1140
  index_type = options[:type].to_s if options.key?(:type)
1141
+ index_type ||= options[:unique] ? "UNIQUE" : ""
879
1142
  index_name = options[:name].to_s if options.key?(:name)
880
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
1143
+ index_name ||= index_name(table_name, column_names)
881
1144
 
882
1145
  if options.key?(:algorithm)
883
1146
  algorithm = index_algorithms.fetch(options[:algorithm]) {
@@ -891,63 +1154,99 @@ module ActiveRecord
891
1154
  index_options = options[:where] ? " WHERE #{options[:where]}" : ""
892
1155
  end
893
1156
 
894
- if index_name.length > max_index_length
895
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
896
- end
897
- 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)
898
1160
  raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
899
1161
  end
900
1162
  index_columns = quoted_columns_for_index(column_names, options).join(", ")
901
1163
 
902
- [index_name, index_type, index_columns, index_options, algorithm, using]
1164
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
903
1165
  end
904
1166
 
905
- protected
906
- def add_index_sort_order(option_strings, column_names, options = {})
907
- if options.is_a?(Hash) && order = options[:order]
908
- case order
909
- when Hash
910
- column_names.each {|name| option_strings[name] += " #{order[name].upcase}" if order.has_key?(name)}
911
- when String
912
- column_names.each {|name| option_strings[name] += " #{order.upcase}"}
913
- end
914
- end
1167
+ def options_include_default?(options)
1168
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1169
+ end
915
1170
 
916
- return option_strings
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
1180
+
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]
1188
+ end
1189
+
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
917
1195
  end
918
1196
 
919
- # Overridden by the MySQL adapter for supporting index lengths
920
- def quoted_columns_for_index(column_names, options = {})
921
- option_strings = Hash[column_names.map {|name| [name, '']}]
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
922
1204
 
923
- # add index sort order if supported
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)
924
1208
  if supports_index_sort_order?
925
- option_strings = add_index_sort_order(option_strings, column_names, options)
1209
+ quoted_columns = add_index_sort_order(quoted_columns, options)
926
1210
  end
927
1211
 
928
- column_names.map {|name| quote_column_name(name) + option_strings[name]}
1212
+ quoted_columns
929
1213
  end
930
1214
 
931
- def options_include_default?(options)
932
- 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
933
1220
  end
934
1221
 
935
1222
  def index_name_for_remove(table_name, options = {})
936
- index_name = index_name(table_name, options)
1223
+ return options[:name] if can_remove_index_by_name?(options)
937
1224
 
938
- unless index_name_exists?(table_name, index_name, true)
939
- if options.is_a?(Hash) && options.has_key?(:name)
940
- options_without_column = options.dup
941
- options_without_column.delete :column
942
- index_name_without_column = index_name(table_name, options_without_column)
1225
+ checks = []
943
1226
 
944
- return index_name_without_column if index_name_exists?(table_name, index_name_without_column, false)
945
- 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
946
1233
 
947
- 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) }
948
1236
  end
949
1237
 
950
- 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
951
1250
  end
952
1251
 
953
1252
  def rename_table_indexes(table_name, new_name)
@@ -972,20 +1271,126 @@ module ActiveRecord
972
1271
  end
973
1272
  end
974
1273
 
975
- private
976
- def create_table_definition(name, temporary, options, as = nil)
977
- TableDefinition.new native_database_types, name, temporary, options, as
978
- end
1274
+ def schema_creation
1275
+ SchemaCreation.new(self)
1276
+ end
979
1277
 
980
- def create_alter_table(name)
981
- AlterTable.new create_table_definition(name, false, {})
982
- end
1278
+ def create_table_definition(*args)
1279
+ TableDefinition.new(*args)
1280
+ end
983
1281
 
984
- def foreign_key_name(table_name, options) # :nodoc:
985
- options.fetch(:name) do
986
- "fk_rails_#{SecureRandom.hex(5)}"
1282
+ def create_alter_table(name)
1283
+ AlterTable.new create_table_definition(name)
1284
+ end
1285
+
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
987
1393
  end
988
- end
989
1394
  end
990
1395
  end
991
1396
  end