activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/compact"
4
+
5
+ module ActiveRecord
6
+ module ConnectionAdapters # :nodoc:
7
+ class SchemaDumper < SchemaDumper # :nodoc:
8
+ def self.create(connection, options)
9
+ new(connection, options)
10
+ end
11
+
12
+ private
13
+ def column_spec(column)
14
+ [schema_type_with_virtual(column), prepare_column_options(column)]
15
+ end
16
+
17
+ def column_spec_for_primary_key(column)
18
+ return {} if default_primary_key?(column)
19
+ spec = { id: schema_type(column).inspect }
20
+ spec.merge!(prepare_column_options(column).except!(:null))
21
+ spec[:default] ||= "nil" if explicit_primary_key_default?(column)
22
+ spec
23
+ end
24
+
25
+ def prepare_column_options(column)
26
+ spec = {}
27
+ spec[:limit] = schema_limit(column)
28
+ spec[:precision] = schema_precision(column)
29
+ spec[:scale] = schema_scale(column)
30
+ spec[:default] = schema_default(column)
31
+ spec[:null] = "false" unless column.null
32
+ spec[:collation] = schema_collation(column)
33
+ spec[:comment] = column.comment.inspect if column.comment.present?
34
+ spec.compact!
35
+ spec
36
+ end
37
+
38
+ def default_primary_key?(column)
39
+ schema_type(column) == :bigint
40
+ end
41
+
42
+ def explicit_primary_key_default?(column)
43
+ false
44
+ end
45
+
46
+ def schema_type_with_virtual(column)
47
+ if @connection.supports_virtual_columns? && column.virtual?
48
+ :virtual
49
+ else
50
+ schema_type(column)
51
+ end
52
+ end
53
+
54
+ def schema_type(column)
55
+ if column.bigint?
56
+ :bigint
57
+ else
58
+ column.type
59
+ end
60
+ end
61
+
62
+ def schema_limit(column)
63
+ limit = column.limit unless column.bigint?
64
+ limit.inspect if limit && limit != @connection.native_database_types[column.type][:limit]
65
+ end
66
+
67
+ def schema_precision(column)
68
+ column.precision.inspect if column.precision
69
+ end
70
+
71
+ def schema_scale(column)
72
+ column.scale.inspect if column.scale
73
+ end
74
+
75
+ def schema_default(column)
76
+ return unless column.has_default?
77
+ type = @connection.lookup_cast_type_from_column(column)
78
+ default = type.deserialize(column.default)
79
+ if default.nil?
80
+ schema_expression(column)
81
+ else
82
+ type.type_cast_for_schema(default)
83
+ end
84
+ end
85
+
86
+ def schema_expression(column)
87
+ "-> { #{column.default_function.inspect} }" if column.default_function
88
+ end
89
+
90
+ def schema_collation(column)
91
+ column.collation.inspect if column.collation
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,1396 @@
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"
6
+
7
+ module ActiveRecord
8
+ module ConnectionAdapters # :nodoc:
9
+ module SchemaStatements
10
+ include ActiveRecord::Migration::JoinTable
11
+
12
+ # Returns a hash of mappings from the abstract data types to the native
13
+ # database types. See TableDefinition#column for details on the recognized
14
+ # abstract data types.
15
+ def native_database_types
16
+ {}
17
+ end
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
+
28
+ # Truncates a table alias according to the limits of the current adapter.
29
+ def table_alias_for(table_name)
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")
54
+ end
55
+
56
+ # Checks to see if the table +table_name+ exists on the database.
57
+ #
58
+ # table_exists?(:developers)
59
+ #
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
63
+ tables.include?(table_name.to_s)
64
+ end
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
+
81
+ # Returns an array of indexes for the given table.
82
+ def indexes(table_name)
83
+ raise NotImplementedError, "#indexes is not implemented"
84
+ end
85
+
86
+ # Checks to see if an index exists on a table for a given index definition.
87
+ #
88
+ # # Check an index exists
89
+ # index_exists?(:suppliers, :company_id)
90
+ #
91
+ # # Check an index on multiple columns exists
92
+ # index_exists?(:suppliers, [:company_id, :company_type])
93
+ #
94
+ # # Check a unique index exists
95
+ # index_exists?(:suppliers, :company_id, unique: true)
96
+ #
97
+ # # Check an index with a custom name exists
98
+ # index_exists?(:suppliers, :company_id, name: "idx_company_id")
99
+ #
100
+ def index_exists?(table_name, column_name, options = {})
101
+ column_names = Array(column_name).map(&:to_s)
102
+ checks = []
103
+ checks << lambda { |i| i.columns == column_names }
104
+ checks << lambda { |i| i.unique } if options[:unique]
105
+ checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
106
+
107
+ indexes(table_name).any? { |i| checks.all? { |check| check[i] } }
108
+ end
109
+
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
117
+
118
+ # Checks to see if a column exists in a given table.
119
+ #
120
+ # # Check a column exists
121
+ # column_exists?(:suppliers, :name)
122
+ #
123
+ # # Check a column exists of a particular type
124
+ # column_exists?(:suppliers, :name, :string)
125
+ #
126
+ # # Check a column exists with a specific definition
127
+ # column_exists?(:suppliers, :name, :string, limit: 100)
128
+ # column_exists?(:suppliers, :name, :string, default: 'default')
129
+ # column_exists?(:suppliers, :name, :string, null: false)
130
+ # column_exists?(:suppliers, :tax, :decimal, precision: 8, scale: 2)
131
+ #
132
+ def column_exists?(table_name, column_name, type = nil, options = {})
133
+ column_name = column_name.to_s
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
149
+ end
150
+
151
+ # Creates a new table with the name +table_name+. +table_name+ may either
152
+ # be a String or a Symbol.
153
+ #
154
+ # There are two ways to work with #create_table. You can use the block
155
+ # form or the regular form, like this:
156
+ #
157
+ # === Block form
158
+ #
159
+ # # create_table() passes a TableDefinition object to the block.
160
+ # # This form will not only create the table, but also columns for the
161
+ # # table.
162
+ #
163
+ # create_table(:suppliers) do |t|
164
+ # t.column :name, :string, limit: 60
165
+ # # Other fields here
166
+ # end
167
+ #
168
+ # === Block form, with shorthand
169
+ #
170
+ # # You can also use the column types as method calls, rather than calling the column method.
171
+ # create_table(:suppliers) do |t|
172
+ # t.string :name, limit: 60
173
+ # # Other fields here
174
+ # end
175
+ #
176
+ # === Regular form
177
+ #
178
+ # # Creates a table called 'suppliers' with no columns.
179
+ # create_table(:suppliers)
180
+ # # Add a column to 'suppliers'.
181
+ # add_column(:suppliers, :name, :string, {limit: 60})
182
+ #
183
+ # The +options+ hash can include the following keys:
184
+ # [<tt>:id</tt>]
185
+ # Whether to automatically add a primary key column. Defaults to true.
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.
189
+ # [<tt>:primary_key</tt>]
190
+ # The name of the primary key, if one is to be added automatically.
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.
194
+ #
195
+ # Note that Active Record models will automatically detect their
196
+ # primary key. This can be avoided by using
197
+ # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
198
+ # to define the key explicitly.
199
+ #
200
+ # [<tt>:options</tt>]
201
+ # Any extra options you want appended to the table definition.
202
+ # [<tt>:temporary</tt>]
203
+ # Make a temporary table.
204
+ # [<tt>:force</tt>]
205
+ # Set to true to drop the table before creating it.
206
+ # Set to +:cascade+ to drop dependent objects as well.
207
+ # Defaults to false.
208
+ # [<tt>:as</tt>]
209
+ # SQL to use to generate the table. When this option is used, the block is
210
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
211
+ #
212
+ # ====== Add a backend specific option to the generated SQL (MySQL)
213
+ #
214
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
215
+ #
216
+ # generates:
217
+ #
218
+ # CREATE TABLE suppliers (
219
+ # id bigint auto_increment PRIMARY KEY
220
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
221
+ #
222
+ # ====== Rename the primary key column
223
+ #
224
+ # create_table(:objects, primary_key: 'guid') do |t|
225
+ # t.column :name, :string, limit: 80
226
+ # end
227
+ #
228
+ # generates:
229
+ #
230
+ # CREATE TABLE objects (
231
+ # guid bigint auto_increment PRIMARY KEY,
232
+ # name varchar(80)
233
+ # )
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
+ #
265
+ # ====== Do not add a primary key column
266
+ #
267
+ # create_table(:categories_suppliers, id: false) do |t|
268
+ # t.column :category_id, :bigint
269
+ # t.column :supplier_id, :bigint
270
+ # end
271
+ #
272
+ # generates:
273
+ #
274
+ # CREATE TABLE categories_suppliers (
275
+ # category_id bigint,
276
+ # supplier_id bigint
277
+ # )
278
+ #
279
+ # ====== Create a temporary table based on a query
280
+ #
281
+ # create_table(:long_query, temporary: true,
282
+ # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
283
+ #
284
+ # generates:
285
+ #
286
+ # CREATE TEMPORARY TABLE long_query AS
287
+ # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
288
+ #
289
+ # See also TableDefinition#column for details on how to create columns.
290
+ def create_table(table_name, comment: nil, **options)
291
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
292
+
293
+ if options[:id] != false && !options[:as]
294
+ pk = options.fetch(:primary_key) do
295
+ Base.get_primary_key table_name.to_s.singularize
296
+ end
297
+
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
303
+ end
304
+
305
+ yield td if block_given?
306
+
307
+ if options[:force]
308
+ drop_table(table_name, options.merge(if_exists: true))
309
+ end
310
+
311
+ result = execute schema_creation.accept td
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
+
327
+ result
328
+ end
329
+
330
+ # Creates a new join table with the name created using the lexical order of the first two
331
+ # arguments. These arguments can be a String or a Symbol.
332
+ #
333
+ # # Creates a table called 'assemblies_parts' with no id.
334
+ # create_join_table(:assemblies, :parts)
335
+ #
336
+ # You can pass an +options+ hash which can include the following keys:
337
+ # [<tt>:table_name</tt>]
338
+ # Sets the table name, overriding the default.
339
+ # [<tt>:column_options</tt>]
340
+ # Any extra options you want appended to the columns definition.
341
+ # [<tt>:options</tt>]
342
+ # Any extra options you want appended to the table definition.
343
+ # [<tt>:temporary</tt>]
344
+ # Make a temporary table.
345
+ # [<tt>:force</tt>]
346
+ # Set to true to drop the table before creating it.
347
+ # Defaults to false.
348
+ #
349
+ # Note that #create_join_table does not create any indices by default; you can use
350
+ # its block form to do so yourself:
351
+ #
352
+ # create_join_table :products, :categories do |t|
353
+ # t.index :product_id
354
+ # t.index :category_id
355
+ # end
356
+ #
357
+ # ====== Add a backend specific option to the generated SQL (MySQL)
358
+ #
359
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
360
+ #
361
+ # generates:
362
+ #
363
+ # CREATE TABLE assemblies_parts (
364
+ # assembly_id bigint NOT NULL,
365
+ # part_id bigint NOT NULL,
366
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
367
+ #
368
+ def create_join_table(table_1, table_2, column_options: {}, **options)
369
+ join_table_name = find_join_table_name(table_1, table_2, options)
370
+
371
+ column_options.reverse_merge!(null: false, index: false)
372
+
373
+ t1_ref, t2_ref = [table_1, table_2].map { |t| t.to_s.singularize }
374
+
375
+ create_table(join_table_name, options.merge!(id: false)) do |td|
376
+ td.references t1_ref, column_options
377
+ td.references t2_ref, column_options
378
+ yield td if block_given?
379
+ end
380
+ end
381
+
382
+ # Drops the join table specified by the given arguments.
383
+ # See #create_join_table for details.
384
+ #
385
+ # Although this command ignores the block if one is given, it can be helpful
386
+ # to provide one in a migration's +change+ method so it can be reverted.
387
+ # In that case, the block will be used by #create_join_table.
388
+ def drop_join_table(table_1, table_2, options = {})
389
+ join_table_name = find_join_table_name(table_1, table_2, options)
390
+ drop_table(join_table_name)
391
+ end
392
+
393
+ # A block for changing columns in +table+.
394
+ #
395
+ # # change_table() yields a Table instance
396
+ # change_table(:suppliers) do |t|
397
+ # t.column :name, :string, limit: 60
398
+ # # Other column alterations here
399
+ # end
400
+ #
401
+ # The +options+ hash can include the following keys:
402
+ # [<tt>:bulk</tt>]
403
+ # Set this to true to make this a bulk alter query, such as
404
+ #
405
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
406
+ #
407
+ # Defaults to false.
408
+ #
409
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
410
+ #
411
+ # ====== Add a column
412
+ #
413
+ # change_table(:suppliers) do |t|
414
+ # t.column :name, :string, limit: 60
415
+ # end
416
+ #
417
+ # ====== Add 2 integer columns
418
+ #
419
+ # change_table(:suppliers) do |t|
420
+ # t.integer :width, :height, null: false, default: 0
421
+ # end
422
+ #
423
+ # ====== Add created_at/updated_at columns
424
+ #
425
+ # change_table(:suppliers) do |t|
426
+ # t.timestamps
427
+ # end
428
+ #
429
+ # ====== Add a foreign key column
430
+ #
431
+ # change_table(:suppliers) do |t|
432
+ # t.references :company
433
+ # end
434
+ #
435
+ # Creates a <tt>company_id(bigint)</tt> column.
436
+ #
437
+ # ====== Add a polymorphic foreign key column
438
+ #
439
+ # change_table(:suppliers) do |t|
440
+ # t.belongs_to :company, polymorphic: true
441
+ # end
442
+ #
443
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
444
+ #
445
+ # ====== Remove a column
446
+ #
447
+ # change_table(:suppliers) do |t|
448
+ # t.remove :company
449
+ # end
450
+ #
451
+ # ====== Remove several columns
452
+ #
453
+ # change_table(:suppliers) do |t|
454
+ # t.remove :company_id
455
+ # t.remove :width, :height
456
+ # end
457
+ #
458
+ # ====== Remove an index
459
+ #
460
+ # change_table(:suppliers) do |t|
461
+ # t.remove_index :company_id
462
+ # end
463
+ #
464
+ # See also Table for details on all of the various column transformations.
465
+ def change_table(table_name, options = {})
466
+ if supports_bulk_alter? && options[:bulk]
467
+ recorder = ActiveRecord::Migration::CommandRecorder.new(self)
468
+ yield update_table_definition(table_name, recorder)
469
+ bulk_change_table(table_name, recorder.commands)
470
+ else
471
+ yield update_table_definition(table_name, self)
472
+ end
473
+ end
474
+
475
+ # Renames a table.
476
+ #
477
+ # rename_table('octopuses', 'octopi')
478
+ #
479
+ def rename_table(table_name, new_name)
480
+ raise NotImplementedError, "rename_table is not implemented"
481
+ end
482
+
483
+ # Drops a table from the database.
484
+ #
485
+ # [<tt>:force</tt>]
486
+ # Set to +:cascade+ to drop dependent objects as well.
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.
491
+ #
492
+ # Although this command ignores most +options+ and the block if one is given,
493
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
494
+ # In that case, +options+ and the block will be used by #create_table.
495
+ def drop_table(table_name, options = {})
496
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
497
+ end
498
+
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
578
+ def add_column(table_name, column_name, type, options = {})
579
+ at = create_alter_table table_name
580
+ at.add_column(column_name, type, options)
581
+ execute schema_creation.accept at
582
+ end
583
+
584
+ # Removes the given columns from the table definition.
585
+ #
586
+ # remove_columns(:suppliers, :qualification, :experience)
587
+ #
588
+ def remove_columns(table_name, *column_names)
589
+ raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
590
+ column_names.each do |column_name|
591
+ remove_column(table_name, column_name)
592
+ end
593
+ end
594
+
595
+ # Removes the column from the table definition.
596
+ #
597
+ # remove_column(:suppliers, :qualification)
598
+ #
599
+ # The +type+ and +options+ parameters will be ignored if present. It can be helpful
600
+ # to provide these in a migration's +change+ method so it can be reverted.
601
+ # In that case, +type+ and +options+ will be used by #add_column.
602
+ def remove_column(table_name, column_name, type = nil, options = {})
603
+ execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}"
604
+ end
605
+
606
+ # Changes the column's definition according to the new options.
607
+ # See TableDefinition#column for details of the options you can use.
608
+ #
609
+ # change_column(:suppliers, :name, :string, limit: 80)
610
+ # change_column(:accounts, :description, :text)
611
+ #
612
+ def change_column(table_name, column_name, type, options = {})
613
+ raise NotImplementedError, "change_column is not implemented"
614
+ end
615
+
616
+ # Sets a new default value for a column:
617
+ #
618
+ # change_column_default(:suppliers, :qualification, 'new')
619
+ # change_column_default(:accounts, :authorized, 1)
620
+ #
621
+ # Setting the default to +nil+ effectively drops the default:
622
+ #
623
+ # change_column_default(:users, :email, nil)
624
+ #
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)
631
+ raise NotImplementedError, "change_column_default is not implemented"
632
+ end
633
+
634
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
635
+ # indicates whether the value can be +NULL+. For example
636
+ #
637
+ # change_column_null(:users, :nickname, false)
638
+ #
639
+ # says nicknames cannot be +NULL+ (adds the constraint), whereas
640
+ #
641
+ # change_column_null(:users, :nickname, true)
642
+ #
643
+ # allows them to be +NULL+ (drops the constraint).
644
+ #
645
+ # The method accepts an optional fourth argument to replace existing
646
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
647
+ # constraint if needed, since otherwise those rows would not be valid.
648
+ #
649
+ # Please note the fourth argument does not set a column's default.
650
+ def change_column_null(table_name, column_name, null, default = nil)
651
+ raise NotImplementedError, "change_column_null is not implemented"
652
+ end
653
+
654
+ # Renames a column.
655
+ #
656
+ # rename_column(:suppliers, :description, :name)
657
+ #
658
+ def rename_column(table_name, column_name, new_column_name)
659
+ raise NotImplementedError, "rename_column is not implemented"
660
+ end
661
+
662
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
663
+ # an Array of Symbols.
664
+ #
665
+ # The index will be named after the table and the column name(s), unless
666
+ # you pass <tt>:name</tt> as an option.
667
+ #
668
+ # ====== Creating a simple index
669
+ #
670
+ # add_index(:suppliers, :name)
671
+ #
672
+ # generates:
673
+ #
674
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
675
+ #
676
+ # ====== Creating a unique index
677
+ #
678
+ # add_index(:accounts, [:branch_id, :party_id], unique: true)
679
+ #
680
+ # generates:
681
+ #
682
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
683
+ #
684
+ # ====== Creating a named index
685
+ #
686
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, name: 'by_branch_party')
687
+ #
688
+ # generates:
689
+ #
690
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
691
+ #
692
+ # ====== Creating an index with specific key length
693
+ #
694
+ # add_index(:accounts, :name, name: 'by_name', length: 10)
695
+ #
696
+ # generates:
697
+ #
698
+ # CREATE INDEX by_name ON accounts(name(10))
699
+ #
700
+ # ====== Creating an index with specific key lengths for multiple keys
701
+ #
702
+ # add_index(:accounts, [:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
703
+ #
704
+ # generates:
705
+ #
706
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
707
+ #
708
+ # Note: SQLite doesn't support index length.
709
+ #
710
+ # ====== Creating an index with a sort order (desc or asc, asc is the default)
711
+ #
712
+ # add_index(:accounts, [:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
713
+ #
714
+ # generates:
715
+ #
716
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
717
+ #
718
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
719
+ #
720
+ # ====== Creating a partial index
721
+ #
722
+ # add_index(:accounts, [:branch_id, :party_id], unique: true, where: "active")
723
+ #
724
+ # generates:
725
+ #
726
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
727
+ #
728
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
729
+ #
730
+ # ====== Creating an index with a specific method
731
+ #
732
+ # add_index(:developers, :name, using: 'btree')
733
+ #
734
+ # generates:
735
+ #
736
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
737
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
738
+ #
739
+ # Note: only supported by PostgreSQL and MySQL
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
+ #
754
+ # ====== Creating an index with a specific type
755
+ #
756
+ # add_index(:developers, :name, type: :fulltext)
757
+ #
758
+ # generates:
759
+ #
760
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
761
+ #
762
+ # Note: only supported by MySQL.
763
+ def add_index(table_name, column_name, options = {})
764
+ index_name, index_type, index_columns, index_options = add_index_options(table_name, column_name, options)
765
+ execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns})#{index_options}"
766
+ end
767
+
768
+ # Removes the given index from the table.
769
+ #
770
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
771
+ #
772
+ # remove_index :accounts, :branch_id
773
+ #
774
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
775
+ #
776
+ # remove_index :accounts, column: :branch_id
777
+ #
778
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
779
+ #
780
+ # remove_index :accounts, column: [:branch_id, :party_id]
781
+ #
782
+ # Removes the index named +by_branch_party+ in the +accounts+ table.
783
+ #
784
+ # remove_index :accounts, name: :by_branch_party
785
+ #
786
+ def remove_index(table_name, options = {})
787
+ index_name = index_name_for_remove(table_name, options)
788
+ execute "DROP INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)}"
789
+ end
790
+
791
+ # Renames an index.
792
+ #
793
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
794
+ #
795
+ # rename_index :people, 'index_people_on_last_name', 'index_users_on_last_name'
796
+ #
797
+ def rename_index(table_name, old_name, new_name)
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)
801
+ old_index_def = indexes(table_name).detect { |i| i.name == old_name }
802
+ return unless old_index_def
803
+ add_index(table_name, old_index_def.columns, name: new_name, unique: old_index_def.unique)
804
+ remove_index(table_name, name: old_name)
805
+ end
806
+
807
+ def index_name(table_name, options) #:nodoc:
808
+ if Hash === options
809
+ if options[:column]
810
+ "index_#{table_name}_on_#{Array(options[:column]) * '_and_'}"
811
+ elsif options[:name]
812
+ options[:name]
813
+ else
814
+ raise ArgumentError, "You must specify the index name"
815
+ end
816
+ else
817
+ index_name(table_name, index_name_options(options))
818
+ end
819
+ end
820
+
821
+ # Verifies the existence of an index with a given name.
822
+ def index_name_exists?(table_name, index_name)
823
+ index_name = index_name.to_s
824
+ indexes(table_name).detect { |i| i.name == index_name }
825
+ end
826
+
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.
831
+ #
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
846
+ #
847
+ # add_reference(:products, :user)
848
+ #
849
+ # ====== Create a user_id string column
850
+ #
851
+ # add_reference(:products, :user, type: :string)
852
+ #
853
+ # ====== Create supplier_id, supplier_type columns and appropriate index
854
+ #
855
+ # add_reference(:products, :supplier, polymorphic: true, index: true)
856
+ #
857
+ # ====== Create a supplier_id column with a unique index
858
+ #
859
+ # add_reference(:products, :supplier, index: { unique: true })
860
+ #
861
+ # ====== Create a supplier_id column with a named index
862
+ #
863
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
864
+ #
865
+ # ====== Create a supplier_id column and appropriate foreign key
866
+ #
867
+ # add_reference(:products, :supplier, foreign_key: true)
868
+ #
869
+ # ====== Create a supplier_id column and a foreign key to the firms table
870
+ #
871
+ # add_reference(:products, :supplier, foreign_key: {to_table: :firms})
872
+ #
873
+ def add_reference(table_name, ref_name, **options)
874
+ ReferenceDefinition.new(ref_name, options).add_to(update_table_definition(table_name, self))
875
+ end
876
+ alias :add_belongs_to :add_reference
877
+
878
+ # Removes the reference(s). Also removes a +type+ column if one exists.
879
+ # #remove_reference and #remove_belongs_to are acceptable.
880
+ #
881
+ # ====== Remove the reference
882
+ #
883
+ # remove_reference(:products, :user, index: true)
884
+ #
885
+ # ====== Remove polymorphic reference
886
+ #
887
+ # remove_reference(:products, :supplier, polymorphic: true)
888
+ #
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
+
905
+ remove_column(table_name, "#{ref_name}_id")
906
+ remove_column(table_name, "#{ref_name}_type") if polymorphic
907
+ end
908
+ alias :remove_belongs_to :remove_reference
909
+
910
+ # Returns an array of foreign keys for the given table.
911
+ # The foreign keys are represented as ForeignKeyDefinition objects.
912
+ def foreign_keys(table_name)
913
+ raise NotImplementedError, "foreign_keys is not implemented"
914
+ end
915
+
916
+ # Adds a new foreign key. +from_table+ is the table with the key column,
917
+ # +to_table+ contains the referenced primary key.
918
+ #
919
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
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.
922
+ #
923
+ # ====== Creating a simple foreign key
924
+ #
925
+ # add_foreign_key :articles, :authors
926
+ #
927
+ # generates:
928
+ #
929
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
930
+ #
931
+ # ====== Creating a foreign key on a specific column
932
+ #
933
+ # add_foreign_key :articles, :users, column: :author_id, primary_key: "lng_id"
934
+ #
935
+ # generates:
936
+ #
937
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
938
+ #
939
+ # ====== Creating a cascading foreign key
940
+ #
941
+ # add_foreign_key :articles, :authors, on_delete: :cascade
942
+ #
943
+ # generates:
944
+ #
945
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
946
+ #
947
+ # The +options+ hash can include the following keys:
948
+ # [<tt>:column</tt>]
949
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
950
+ # [<tt>:primary_key</tt>]
951
+ # The primary key column name on +to_table+. Defaults to +id+.
952
+ # [<tt>:name</tt>]
953
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
954
+ # [<tt>:on_delete</tt>]
955
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
956
+ # [<tt>:on_update</tt>]
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+.
960
+ def add_foreign_key(from_table, to_table, options = {})
961
+ return unless supports_foreign_keys?
962
+
963
+ options = foreign_key_options(from_table, to_table, options)
964
+ at = create_alter_table from_table
965
+ at.add_foreign_key to_table, options
966
+
967
+ execute schema_creation.accept(at)
968
+ end
969
+
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.
974
+ #
975
+ # Removes the foreign key on +accounts.branch_id+.
976
+ #
977
+ # remove_foreign_key :accounts, :branches
978
+ #
979
+ # Removes the foreign key on +accounts.owner_id+.
980
+ #
981
+ # remove_foreign_key :accounts, column: :owner_id
982
+ #
983
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
984
+ #
985
+ # remove_foreign_key :accounts, name: :special_fk_name
986
+ #
987
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key.
988
+ def remove_foreign_key(from_table, options_or_to_table = {})
989
+ return unless supports_foreign_keys?
990
+
991
+ fk_name_to_delete = foreign_key_for!(from_table, options_or_to_table).name
992
+
993
+ at = create_alter_table from_table
994
+ at.drop_foreign_key fk_name_to_delete
995
+
996
+ execute schema_creation.accept(at)
997
+ end
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
+
1014
+ def foreign_key_column_for(table_name) # :nodoc:
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"
1019
+ end
1020
+
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
1027
+
1028
+ def dump_schema_information #:nodoc:
1029
+ versions = ActiveRecord::SchemaMigration.all_versions
1030
+ insert_versions_sql(versions) if versions.any?
1031
+ end
1032
+
1033
+ def internal_string_options_for_primary_key # :nodoc:
1034
+ { primary_key: true }
1035
+ end
1036
+
1037
+ def assume_migrated_upto_version(version, migrations_paths)
1038
+ migrations_paths = Array(migrations_paths)
1039
+ version = version.to_i
1040
+ sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1041
+
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
1045
+ end
1046
+
1047
+ unless migrated.include?(version)
1048
+ execute "INSERT INTO #{sm_table} (version) VALUES (#{quote(version)})"
1049
+ end
1050
+
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
1062
+ end
1063
+ end
1064
+ end
1065
+
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]
1069
+ column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
1070
+
1071
+ if type == :decimal # ignore limit, use precision and scale
1072
+ scale ||= native[:scale]
1073
+
1074
+ if precision ||= native[:precision]
1075
+ if scale
1076
+ column_type_sql << "(#{precision},#{scale})"
1077
+ else
1078
+ column_type_sql << "(#{precision})"
1079
+ end
1080
+ elsif scale
1081
+ raise ArgumentError, "Error adding decimal column: precision cannot be empty if scale is specified"
1082
+ end
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
1090
+ elsif (type != :primary_key) && (limit ||= native.is_a?(Hash) && native[:limit])
1091
+ column_type_sql << "(#{limit})"
1092
+ end
1093
+
1094
+ column_type_sql
1095
+ else
1096
+ type.to_s
1097
+ end
1098
+ end
1099
+
1100
+ # Given a set of columns and an ORDER BY clause, returns the columns for a SELECT DISTINCT.
1101
+ # PostgreSQL, MySQL, and Oracle override this for custom DISTINCT syntax - they
1102
+ # require the order columns appear in the SELECT.
1103
+ #
1104
+ # columns_for_distinct("posts.id", ["posts.created_at desc"])
1105
+ #
1106
+ def columns_for_distinct(columns, orders) # :nodoc:
1107
+ columns
1108
+ end
1109
+
1110
+ # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
1111
+ # Additional options (like +:null+) are forwarded to #add_column.
1112
+ #
1113
+ # add_timestamps(:suppliers, null: true)
1114
+ #
1115
+ def add_timestamps(table_name, options = {})
1116
+ options[:null] = false if options[:null].nil?
1117
+
1118
+ add_column table_name, :created_at, :datetime, options
1119
+ add_column table_name, :updated_at, :datetime, options
1120
+ end
1121
+
1122
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
1123
+ #
1124
+ # remove_timestamps(:suppliers)
1125
+ #
1126
+ def remove_timestamps(table_name, options = {})
1127
+ remove_column table_name, :updated_at
1128
+ remove_column table_name, :created_at
1129
+ end
1130
+
1131
+ def update_table_definition(table_name, base) #:nodoc:
1132
+ Table.new(table_name, base)
1133
+ end
1134
+
1135
+ def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc:
1136
+ column_names = index_column_names(column_name)
1137
+
1138
+ options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass)
1139
+
1140
+ index_type = options[:type].to_s if options.key?(:type)
1141
+ index_type ||= options[:unique] ? "UNIQUE" : ""
1142
+ index_name = options[:name].to_s if options.key?(:name)
1143
+ index_name ||= index_name(table_name, column_names)
1144
+
1145
+ if options.key?(:algorithm)
1146
+ algorithm = index_algorithms.fetch(options[:algorithm]) {
1147
+ raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
1148
+ }
1149
+ end
1150
+
1151
+ using = "USING #{options[:using]}" if options[:using].present?
1152
+
1153
+ if supports_partial_index?
1154
+ index_options = options[:where] ? " WHERE #{options[:where]}" : ""
1155
+ end
1156
+
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)
1160
+ raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
1161
+ end
1162
+ index_columns = quoted_columns_for_index(column_names, options).join(", ")
1163
+
1164
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
1165
+ end
1166
+
1167
+ def options_include_default?(options)
1168
+ options.include?(:default) && !(options[:null] == false && options[:default].nil?)
1169
+ end
1170
+
1171
+ # Changes the comment for a table or removes it if +nil+.
1172
+ def change_table_comment(table_name, comment)
1173
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
1174
+ end
1175
+
1176
+ # Changes the comment for a column or removes it if +nil+.
1177
+ def change_column_comment(table_name, column_name, comment)
1178
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
1179
+ end
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
1195
+ end
1196
+
1197
+ def options_for_index_columns(options)
1198
+ if options.is_a?(Hash)
1199
+ options.symbolize_keys
1200
+ else
1201
+ Hash.new { |hash, column| hash[column] = options }
1202
+ end
1203
+ end
1204
+
1205
+ # Overridden by the MySQL adapter for supporting index lengths and by
1206
+ # the PostgreSQL adapter for supporting operator classes.
1207
+ def add_options_for_index_columns(quoted_columns, **options)
1208
+ if supports_index_sort_order?
1209
+ quoted_columns = add_index_sort_order(quoted_columns, options)
1210
+ end
1211
+
1212
+ quoted_columns
1213
+ end
1214
+
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
1220
+ end
1221
+
1222
+ def index_name_for_remove(table_name, options = {})
1223
+ return options[:name] if can_remove_index_by_name?(options)
1224
+
1225
+ checks = []
1226
+
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
1233
+
1234
+ if column_names.present?
1235
+ checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1236
+ end
1237
+
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
1250
+ end
1251
+
1252
+ def rename_table_indexes(table_name, new_name)
1253
+ indexes(new_name).each do |index|
1254
+ generated_index_name = index_name(table_name, column: index.columns)
1255
+ if generated_index_name == index.name
1256
+ rename_index new_name, generated_index_name, index_name(new_name, column: index.columns)
1257
+ end
1258
+ end
1259
+ end
1260
+
1261
+ def rename_column_indexes(table_name, column_name, new_column_name)
1262
+ column_name, new_column_name = column_name.to_s, new_column_name.to_s
1263
+ indexes(table_name).each do |index|
1264
+ next unless index.columns.include?(new_column_name)
1265
+ old_columns = index.columns.dup
1266
+ old_columns[old_columns.index(new_column_name)] = column_name
1267
+ generated_index_name = index_name(table_name, column: old_columns)
1268
+ if generated_index_name == index.name
1269
+ rename_index table_name, generated_index_name, index_name(table_name, column: index.columns)
1270
+ end
1271
+ end
1272
+ end
1273
+
1274
+ def schema_creation
1275
+ SchemaCreation.new(self)
1276
+ end
1277
+
1278
+ def create_table_definition(*args)
1279
+ TableDefinition.new(*args)
1280
+ end
1281
+
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
1393
+ end
1394
+ end
1395
+ end
1396
+ end