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.

Potentially problematic release.


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

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