activerecord 7.0.4 → 7.1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1971 -1243
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +20 -4
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +20 -14
  15. data/lib/active_record/associations/collection_proxy.rb +20 -10
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency/join_association.rb +3 -2
  21. data/lib/active_record/associations/join_dependency.rb +10 -10
  22. data/lib/active_record/associations/preloader/association.rb +31 -7
  23. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  24. data/lib/active_record/associations/preloader.rb +13 -10
  25. data/lib/active_record/associations/singular_association.rb +1 -1
  26. data/lib/active_record/associations/through_association.rb +22 -11
  27. data/lib/active_record/associations.rb +333 -222
  28. data/lib/active_record/attribute_assignment.rb +0 -2
  29. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  30. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  31. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  32. data/lib/active_record/attribute_methods/query.rb +28 -16
  33. data/lib/active_record/attribute_methods/read.rb +21 -8
  34. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -4
  36. data/lib/active_record/attribute_methods/write.rb +6 -6
  37. data/lib/active_record/attribute_methods.rb +148 -26
  38. data/lib/active_record/attributes.rb +3 -3
  39. data/lib/active_record/autosave_association.rb +59 -10
  40. data/lib/active_record/base.rb +7 -2
  41. data/lib/active_record/callbacks.rb +16 -32
  42. data/lib/active_record/coders/column_serializer.rb +61 -0
  43. data/lib/active_record/coders/json.rb +1 -1
  44. data/lib/active_record/coders/yaml_column.rb +70 -42
  45. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  46. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  47. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +80 -50
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +51 -7
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +155 -25
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +297 -127
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +509 -103
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +254 -125
  60. data/lib/active_record/connection_adapters/column.rb +9 -0
  61. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -14
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +19 -13
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  71. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  78. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  80. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  81. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  82. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  83. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +365 -61
  85. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +354 -193
  87. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  88. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +9 -5
  91. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +213 -85
  94. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
  97. data/lib/active_record/connection_adapters.rb +3 -1
  98. data/lib/active_record/connection_handling.rb +72 -95
  99. data/lib/active_record/core.rb +181 -154
  100. data/lib/active_record/counter_cache.rb +52 -27
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
  102. data/lib/active_record/database_configurations/database_config.rb +9 -3
  103. data/lib/active_record/database_configurations/hash_config.rb +28 -14
  104. data/lib/active_record/database_configurations/url_config.rb +17 -11
  105. data/lib/active_record/database_configurations.rb +86 -33
  106. data/lib/active_record/delegated_type.rb +15 -10
  107. data/lib/active_record/deprecator.rb +7 -0
  108. data/lib/active_record/destroy_association_async_job.rb +3 -1
  109. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  110. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  111. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  112. data/lib/active_record/encryption/config.rb +25 -1
  113. data/lib/active_record/encryption/configurable.rb +12 -19
  114. data/lib/active_record/encryption/context.rb +10 -3
  115. data/lib/active_record/encryption/contexts.rb +5 -1
  116. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  117. data/lib/active_record/encryption/encryptable_record.rb +42 -18
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
  119. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  120. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  121. data/lib/active_record/encryption/key_generator.rb +12 -1
  122. data/lib/active_record/encryption/message_serializer.rb +2 -0
  123. data/lib/active_record/encryption/properties.rb +3 -3
  124. data/lib/active_record/encryption/scheme.rb +22 -21
  125. data/lib/active_record/encryption.rb +3 -0
  126. data/lib/active_record/enum.rb +112 -28
  127. data/lib/active_record/errors.rb +112 -18
  128. data/lib/active_record/explain.rb +23 -3
  129. data/lib/active_record/explain_subscriber.rb +1 -1
  130. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  131. data/lib/active_record/fixture_set/render_context.rb +2 -0
  132. data/lib/active_record/fixture_set/table_row.rb +29 -8
  133. data/lib/active_record/fixtures.rb +135 -71
  134. data/lib/active_record/future_result.rb +40 -5
  135. data/lib/active_record/gem_version.rb +4 -4
  136. data/lib/active_record/inheritance.rb +30 -16
  137. data/lib/active_record/insert_all.rb +57 -10
  138. data/lib/active_record/integration.rb +8 -8
  139. data/lib/active_record/internal_metadata.rb +120 -30
  140. data/lib/active_record/locking/optimistic.rb +33 -19
  141. data/lib/active_record/locking/pessimistic.rb +5 -2
  142. data/lib/active_record/log_subscriber.rb +29 -12
  143. data/lib/active_record/marshalling.rb +59 -0
  144. data/lib/active_record/message_pack.rb +124 -0
  145. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  146. data/lib/active_record/middleware/database_selector.rb +9 -11
  147. data/lib/active_record/middleware/shard_selector.rb +3 -1
  148. data/lib/active_record/migration/command_recorder.rb +105 -7
  149. data/lib/active_record/migration/compatibility.rb +163 -58
  150. data/lib/active_record/migration/default_strategy.rb +23 -0
  151. data/lib/active_record/migration/execution_strategy.rb +19 -0
  152. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  153. data/lib/active_record/migration.rb +271 -114
  154. data/lib/active_record/model_schema.rb +69 -44
  155. data/lib/active_record/nested_attributes.rb +37 -8
  156. data/lib/active_record/normalization.rb +167 -0
  157. data/lib/active_record/persistence.rb +195 -42
  158. data/lib/active_record/promise.rb +84 -0
  159. data/lib/active_record/query_cache.rb +4 -22
  160. data/lib/active_record/query_logs.rb +87 -51
  161. data/lib/active_record/query_logs_formatter.rb +41 -0
  162. data/lib/active_record/querying.rb +15 -2
  163. data/lib/active_record/railtie.rb +107 -45
  164. data/lib/active_record/railties/controller_runtime.rb +14 -9
  165. data/lib/active_record/railties/databases.rake +144 -150
  166. data/lib/active_record/railties/job_runtime.rb +23 -0
  167. data/lib/active_record/readonly_attributes.rb +32 -5
  168. data/lib/active_record/reflection.rb +189 -45
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  170. data/lib/active_record/relation/batches.rb +190 -61
  171. data/lib/active_record/relation/calculations.rb +232 -81
  172. data/lib/active_record/relation/delegation.rb +23 -9
  173. data/lib/active_record/relation/finder_methods.rb +77 -16
  174. data/lib/active_record/relation/merger.rb +2 -0
  175. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  177. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  178. data/lib/active_record/relation/predicate_builder.rb +26 -14
  179. data/lib/active_record/relation/query_attribute.rb +25 -1
  180. data/lib/active_record/relation/query_methods.rb +408 -76
  181. data/lib/active_record/relation/spawn_methods.rb +18 -1
  182. data/lib/active_record/relation.rb +103 -37
  183. data/lib/active_record/result.rb +25 -9
  184. data/lib/active_record/runtime_registry.rb +24 -1
  185. data/lib/active_record/sanitization.rb +51 -11
  186. data/lib/active_record/schema.rb +2 -3
  187. data/lib/active_record/schema_dumper.rb +50 -7
  188. data/lib/active_record/schema_migration.rb +68 -33
  189. data/lib/active_record/scoping/default.rb +15 -5
  190. data/lib/active_record/scoping/named.rb +2 -2
  191. data/lib/active_record/scoping.rb +2 -1
  192. data/lib/active_record/secure_password.rb +60 -0
  193. data/lib/active_record/secure_token.rb +21 -3
  194. data/lib/active_record/signed_id.rb +7 -5
  195. data/lib/active_record/store.rb +9 -9
  196. data/lib/active_record/suppressor.rb +3 -1
  197. data/lib/active_record/table_metadata.rb +16 -3
  198. data/lib/active_record/tasks/database_tasks.rb +152 -108
  199. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  200. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  201. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  202. data/lib/active_record/test_fixtures.rb +114 -96
  203. data/lib/active_record/timestamp.rb +30 -16
  204. data/lib/active_record/token_for.rb +113 -0
  205. data/lib/active_record/touch_later.rb +11 -6
  206. data/lib/active_record/transactions.rb +39 -13
  207. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  208. data/lib/active_record/type/internal/timezone.rb +7 -2
  209. data/lib/active_record/type/serialized.rb +8 -4
  210. data/lib/active_record/type/time.rb +4 -0
  211. data/lib/active_record/validations/absence.rb +1 -1
  212. data/lib/active_record/validations/numericality.rb +5 -4
  213. data/lib/active_record/validations/presence.rb +5 -28
  214. data/lib/active_record/validations/uniqueness.rb +47 -2
  215. data/lib/active_record/validations.rb +8 -4
  216. data/lib/active_record/version.rb +1 -1
  217. data/lib/active_record.rb +130 -17
  218. data/lib/arel/errors.rb +10 -0
  219. data/lib/arel/factory_methods.rb +4 -0
  220. data/lib/arel/filter_predications.rb +1 -1
  221. data/lib/arel/nodes/and.rb +4 -0
  222. data/lib/arel/nodes/binary.rb +6 -1
  223. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  224. data/lib/arel/nodes/cte.rb +36 -0
  225. data/lib/arel/nodes/filter.rb +1 -1
  226. data/lib/arel/nodes/fragments.rb +35 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  228. data/lib/arel/nodes/leading_join.rb +8 -0
  229. data/lib/arel/nodes/node.rb +111 -2
  230. data/lib/arel/nodes/sql_literal.rb +6 -0
  231. data/lib/arel/nodes/table_alias.rb +4 -0
  232. data/lib/arel/nodes.rb +4 -0
  233. data/lib/arel/predications.rb +2 -0
  234. data/lib/arel/table.rb +9 -5
  235. data/lib/arel/tree_manager.rb +5 -1
  236. data/lib/arel/visitors/mysql.rb +8 -1
  237. data/lib/arel/visitors/to_sql.rb +83 -18
  238. data/lib/arel/visitors/visitor.rb +2 -2
  239. data/lib/arel.rb +16 -2
  240. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  241. data/lib/rails/generators/active_record/migration.rb +3 -1
  242. data/lib/rails/generators/active_record/model/USAGE +113 -0
  243. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  244. metadata +51 -15
  245. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  246. data/lib/active_record/null_relation.rb +0 -63
@@ -14,8 +14,10 @@ module ActiveRecord
14
14
  end
15
15
 
16
16
  delegate :quote_column_name, :quote_table_name, :quote_default_expression, :type_to_sql,
17
- :options_include_default?, :supports_indexes_in_create?, :supports_foreign_keys?,
17
+ :options_include_default?, :supports_indexes_in_create?, :use_foreign_keys?,
18
18
  :quoted_columns_for_index, :supports_partial_index?, :supports_check_constraints?,
19
+ :supports_index_include?, :supports_exclusion_constraints?, :supports_unique_constraints?,
20
+ :supports_nulls_not_distinct?,
19
21
  to: :@conn, private: true
20
22
 
21
23
  private
@@ -51,7 +53,7 @@ module ActiveRecord
51
53
  statements.concat(o.indexes.map { |column_name, options| index_in_create(o.name, column_name, options) })
52
54
  end
53
55
 
54
- if supports_foreign_keys?
56
+ if use_foreign_keys?
55
57
  statements.concat(o.foreign_keys.map { |fk| accept fk })
56
58
  end
57
59
 
@@ -59,6 +61,14 @@ module ActiveRecord
59
61
  statements.concat(o.check_constraints.map { |chk| accept chk })
60
62
  end
61
63
 
64
+ if supports_exclusion_constraints?
65
+ statements.concat(o.exclusion_constraints.map { |exc| accept exc })
66
+ end
67
+
68
+ if supports_unique_constraints?
69
+ statements.concat(o.unique_constraints.map { |exc| accept exc })
70
+ end
71
+
62
72
  create_sql << "(#{statements.join(', ')})" if statements.present?
63
73
  add_table_options!(create_sql, o)
64
74
  create_sql << " AS #{to_sql(o.as)}" if o.as
@@ -70,10 +80,12 @@ module ActiveRecord
70
80
  end
71
81
 
72
82
  def visit_ForeignKeyDefinition(o)
83
+ quoted_columns = Array(o.column).map { |c| quote_column_name(c) }
84
+ quoted_primary_keys = Array(o.primary_key).map { |c| quote_column_name(c) }
73
85
  sql = +<<~SQL
74
86
  CONSTRAINT #{quote_column_name(o.name)}
75
- FOREIGN KEY (#{quote_column_name(o.column)})
76
- REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)})
87
+ FOREIGN KEY (#{quoted_columns.join(", ")})
88
+ REFERENCES #{quote_table_name(o.to_table)} (#{quoted_primary_keys.join(", ")})
77
89
  SQL
78
90
  sql << " #{action_sql('DELETE', o.on_delete)}" if o.on_delete
79
91
  sql << " #{action_sql('UPDATE', o.on_update)}" if o.on_update
@@ -100,6 +112,8 @@ module ActiveRecord
100
112
  sql << "#{quote_column_name(index.name)} ON #{quote_table_name(index.table)}"
101
113
  sql << "USING #{index.using}" if supports_index_using? && index.using
102
114
  sql << "(#{quoted_columns(index)})"
115
+ sql << "INCLUDE (#{quoted_include_columns(index.include)})" if supports_index_include? && index.include
116
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && index.nulls_not_distinct
103
117
  sql << "WHERE #{index.where}" if supports_partial_index? && index.where
104
118
 
105
119
  sql.join(" ")
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module ActiveRecord
4
5
  module ConnectionAdapters # :nodoc:
5
6
  # Abstract representation of an index definition on a table. Instances of
6
7
  # this type are typically created and returned by methods in database
7
8
  # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes
8
9
  class IndexDefinition # :nodoc:
9
- attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment
10
+ attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :include, :nulls_not_distinct, :comment, :valid
10
11
 
11
12
  def initialize(
12
13
  table, name,
@@ -18,7 +19,10 @@ module ActiveRecord
18
19
  where: nil,
19
20
  type: nil,
20
21
  using: nil,
21
- comment: nil
22
+ include: nil,
23
+ nulls_not_distinct: nil,
24
+ comment: nil,
25
+ valid: true
22
26
  )
23
27
  @table = table
24
28
  @name = name
@@ -30,7 +34,14 @@ module ActiveRecord
30
34
  @where = where
31
35
  @type = type
32
36
  @using = using
37
+ @include = include
38
+ @nulls_not_distinct = nulls_not_distinct
33
39
  @comment = comment
40
+ @valid = valid
41
+ end
42
+
43
+ def valid?
44
+ @valid
34
45
  end
35
46
 
36
47
  def column_options
@@ -41,6 +52,16 @@ module ActiveRecord
41
52
  }
42
53
  end
43
54
 
55
+ def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, **options)
56
+ columns = options[:column] if columns.blank?
57
+ (columns.nil? || Array(self.columns) == Array(columns).map(&:to_s)) &&
58
+ (name.nil? || self.name == name.to_s) &&
59
+ (unique.nil? || self.unique == unique) &&
60
+ (valid.nil? || self.valid == valid) &&
61
+ (include.nil? || Array(self.include) == Array(include).map(&:to_s)) &&
62
+ (nulls_not_distinct.nil? || self.nulls_not_distinct == nulls_not_distinct)
63
+ end
64
+
44
65
  private
45
66
  def concise_options(options)
46
67
  if columns.size == options.size && options.values.uniq.size == 1
@@ -56,11 +77,24 @@ module ActiveRecord
56
77
  # +columns+ attribute of said TableDefinition object, in order to be used
57
78
  # for generating a number of table creation or table changing SQL statements.
58
79
  ColumnDefinition = Struct.new(:name, :type, :options, :sql_type) do # :nodoc:
80
+ self::OPTION_NAMES = [
81
+ :limit,
82
+ :precision,
83
+ :scale,
84
+ :default,
85
+ :null,
86
+ :collation,
87
+ :comment,
88
+ :primary_key,
89
+ :if_exists,
90
+ :if_not_exists
91
+ ]
92
+
59
93
  def primary_key?
60
94
  options[:primary_key]
61
95
  end
62
96
 
63
- [:limit, :precision, :scale, :default, :null, :collation, :comment].each do |option_name|
97
+ (self::OPTION_NAMES - [:primary_key]).each do |option_name|
64
98
  module_eval <<-CODE, __FILE__, __LINE__ + 1
65
99
  def #{option_name}
66
100
  options[:#{option_name}]
@@ -81,6 +115,8 @@ module ActiveRecord
81
115
 
82
116
  ChangeColumnDefinition = Struct.new(:column, :name) # :nodoc:
83
117
 
118
+ ChangeColumnDefaultDefinition = Struct.new(:column, :default) # :nodoc:
119
+
84
120
  CreateIndexDefinition = Struct.new(:index, :algorithm, :if_not_exists) # :nodoc:
85
121
 
86
122
  PrimaryKeyDefinition = Struct.new(:name) # :nodoc:
@@ -125,8 +161,8 @@ module ActiveRecord
125
161
 
126
162
  def defined_for?(to_table: nil, validate: nil, **options)
127
163
  (to_table.nil? || to_table.to_s == self.to_table) &&
128
- (validate.nil? || validate == options.fetch(:validate, validate)) &&
129
- options.all? { |k, v| self.options[k].to_s == v.to_s }
164
+ (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
165
+ options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
130
166
  end
131
167
 
132
168
  private
@@ -148,6 +184,12 @@ module ActiveRecord
148
184
  def export_name_on_schema_dump?
149
185
  !ActiveRecord::SchemaDumper.chk_ignore_pattern.match?(name) if name
150
186
  end
187
+
188
+ def defined_for?(name:, expression: nil, validate: nil, **options)
189
+ self.name == name.to_s &&
190
+ (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
191
+ options.all? { |k, v| self.options[k].to_s == v.to_s }
192
+ end
151
193
  end
152
194
 
153
195
  class ReferenceDefinition # :nodoc:
@@ -171,6 +213,20 @@ module ActiveRecord
171
213
  end
172
214
  end
173
215
 
216
+ def add(table_name, connection)
217
+ columns.each do |name, type, options|
218
+ connection.add_column(table_name, name, type, **options)
219
+ end
220
+
221
+ if index
222
+ connection.add_index(table_name, column_names, **index_options(table_name))
223
+ end
224
+
225
+ if foreign_key
226
+ connection.add_foreign_key(table_name, foreign_table_name, **foreign_key_options)
227
+ end
228
+ end
229
+
174
230
  def add_to(table)
175
231
  columns.each do |name, type, options|
176
232
  table.column(name, type, **options)
@@ -192,8 +248,12 @@ module ActiveRecord
192
248
  value.is_a?(Hash) ? value : {}
193
249
  end
194
250
 
251
+ def conditional_options
252
+ options.slice(:if_exists, :if_not_exists)
253
+ end
254
+
195
255
  def polymorphic_options
196
- as_options(polymorphic).merge(options.slice(:null, :first, :after))
256
+ as_options(polymorphic).merge(conditional_options).merge(options.slice(:null, :first, :after))
197
257
  end
198
258
 
199
259
  def polymorphic_index_name(table_name)
@@ -201,7 +261,7 @@ module ActiveRecord
201
261
  end
202
262
 
203
263
  def index_options(table_name)
204
- index_options = as_options(index)
264
+ index_options = as_options(index).merge(conditional_options)
205
265
 
206
266
  # legacy reference index names are used on versions 6.0 and earlier
207
267
  return index_options if options[:_uses_legacy_reference_index_name]
@@ -211,7 +271,7 @@ module ActiveRecord
211
271
  end
212
272
 
213
273
  def foreign_key_options
214
- as_options(foreign_key).merge(column: column_name)
274
+ as_options(foreign_key).merge(column: column_name, **conditional_options)
215
275
  end
216
276
 
217
277
  def columns
@@ -280,13 +340,15 @@ module ActiveRecord
280
340
  end
281
341
  end
282
342
 
343
+ # = Active Record Connection Adapters \Table \Definition
344
+ #
283
345
  # Represents the schema of an SQL table in an abstract way. This class
284
346
  # provides methods for manipulating the schema representation.
285
347
  #
286
348
  # Inside migration files, the +t+ object in {create_table}[rdoc-ref:SchemaStatements#create_table]
287
349
  # is actually of this type:
288
350
  #
289
- # class SomeMigration < ActiveRecord::Migration[7.0]
351
+ # class SomeMigration < ActiveRecord::Migration[7.1]
290
352
  # def up
291
353
  # create_table :foo do |t|
292
354
  # puts t.class # => "ActiveRecord::ConnectionAdapters::TableDefinition"
@@ -327,6 +389,23 @@ module ActiveRecord
327
389
  @comment = comment
328
390
  end
329
391
 
392
+ def set_primary_key(table_name, id, primary_key, **options)
393
+ if id && !as
394
+ pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
395
+
396
+ if id.is_a?(Hash)
397
+ options.merge!(id.except(:type))
398
+ id = id.fetch(:type, :primary_key)
399
+ end
400
+
401
+ if pk.is_a?(Array)
402
+ primary_keys(pk)
403
+ else
404
+ primary_key(pk, id, **options)
405
+ end
406
+ end
407
+ end
408
+
330
409
  def primary_keys(name = nil) # :nodoc:
331
410
  @primary_keys = PrimaryKeyDefinition.new(name) if name
332
411
  @primary_keys
@@ -411,20 +490,7 @@ module ActiveRecord
411
490
  name = name.to_s
412
491
  type = type.to_sym if type
413
492
 
414
- if @columns_hash[name]
415
- if @columns_hash[name].primary_key?
416
- raise ArgumentError, "you can't redefine the primary key column '#{name}'. To define a custom primary key, pass { id: false } to create_table."
417
- else
418
- raise ArgumentError, "you can't define an already defined column '#{name}'."
419
- end
420
- end
421
-
422
- if @conn.supports_datetime_with_precision?
423
- if type == :datetime && !options.key?(:precision)
424
- options[:precision] = 6
425
- end
426
- end
427
-
493
+ raise_on_duplicate_column(name)
428
494
  @columns_hash[name] = new_column_definition(name, type, **options)
429
495
 
430
496
  if index
@@ -491,6 +557,13 @@ module ActiveRecord
491
557
  type = integer_like_primary_key_type(type, options)
492
558
  end
493
559
  type = aliased_types(type.to_s, type)
560
+
561
+ if @conn.supports_datetime_with_precision?
562
+ if type == :datetime && !options.key?(:precision)
563
+ options[:precision] = 6
564
+ end
565
+ end
566
+
494
567
  options[:primary_key] ||= type == :primary_key
495
568
  options[:null] = false if options[:primary_key]
496
569
  create_column_definition(name, type, options)
@@ -510,7 +583,15 @@ module ActiveRecord
510
583
  end
511
584
 
512
585
  private
586
+ def valid_column_definition_options
587
+ @conn.valid_column_definition_options
588
+ end
589
+
513
590
  def create_column_definition(name, type, options)
591
+ unless options[:_skip_validate_options]
592
+ options.except(:_uses_legacy_reference_index_name, :_skip_validate_options).assert_valid_keys(valid_column_definition_options)
593
+ end
594
+
514
595
  ColumnDefinition.new(name, type, options)
515
596
  end
516
597
 
@@ -525,6 +606,16 @@ module ActiveRecord
525
606
  def integer_like_primary_key_type(type, options)
526
607
  type
527
608
  end
609
+
610
+ def raise_on_duplicate_column(name)
611
+ if @columns_hash[name]
612
+ if @columns_hash[name].primary_key?
613
+ raise ArgumentError, "you can't redefine the primary key column '#{name}' on '#{@name}'. To define a custom primary key, pass { id: false } to create_table."
614
+ else
615
+ raise ArgumentError, "you can't define an already defined column '#{name}' on '#{@name}'."
616
+ end
617
+ end
618
+ end
528
619
  end
529
620
 
530
621
  class AlterTable # :nodoc:
@@ -566,6 +657,8 @@ module ActiveRecord
566
657
  end
567
658
  end
568
659
 
660
+ # = Active Record Connection Adapters \Table
661
+ #
569
662
  # Represents an SQL table in an abstract way for updating a table.
570
663
  # Also see TableDefinition and {connection.create_table}[rdoc-ref:SchemaStatements#create_table]
571
664
  #
@@ -626,6 +719,7 @@ module ActiveRecord
626
719
  #
627
720
  # See TableDefinition#column for details of the options you can use.
628
721
  def column(column_name, type, index: nil, **options)
722
+ raise_on_if_exist_options(options)
629
723
  @base.add_column(name, column_name, type, **options)
630
724
  if index
631
725
  index_options = index.is_a?(Hash) ? index : {}
@@ -651,6 +745,7 @@ module ActiveRecord
651
745
  #
652
746
  # See {connection.add_index}[rdoc-ref:SchemaStatements#add_index] for details of the options you can use.
653
747
  def index(column_name, **options)
748
+ raise_on_if_exist_options(options)
654
749
  @base.add_index(name, column_name, **options)
655
750
  end
656
751
 
@@ -661,8 +756,8 @@ module ActiveRecord
661
756
  # end
662
757
  #
663
758
  # See {connection.index_exists?}[rdoc-ref:SchemaStatements#index_exists?]
664
- def index_exists?(column_name, options = {})
665
- @base.index_exists?(name, column_name, options)
759
+ def index_exists?(column_name, **options)
760
+ @base.index_exists?(name, column_name, **options)
666
761
  end
667
762
 
668
763
  # Renames the given index on the table.
@@ -680,6 +775,7 @@ module ActiveRecord
680
775
  #
681
776
  # See {connection.add_timestamps}[rdoc-ref:SchemaStatements#add_timestamps]
682
777
  def timestamps(**options)
778
+ raise_on_if_exist_options(options)
683
779
  @base.add_timestamps(name, **options)
684
780
  end
685
781
 
@@ -690,6 +786,7 @@ module ActiveRecord
690
786
  #
691
787
  # See TableDefinition#column for details of the options you can use.
692
788
  def change(column_name, type, **options)
789
+ raise_on_if_exist_options(options)
693
790
  @base.change_column(name, column_name, type, **options)
694
791
  end
695
792
 
@@ -721,6 +818,7 @@ module ActiveRecord
721
818
  #
722
819
  # See {connection.remove_columns}[rdoc-ref:SchemaStatements#remove_columns]
723
820
  def remove(*column_names, **options)
821
+ raise_on_if_exist_options(options)
724
822
  @base.remove_columns(name, *column_names, **options)
725
823
  end
726
824
 
@@ -733,6 +831,7 @@ module ActiveRecord
733
831
  #
734
832
  # See {connection.remove_index}[rdoc-ref:SchemaStatements#remove_index]
735
833
  def remove_index(column_name = nil, **options)
834
+ raise_on_if_exist_options(options)
736
835
  @base.remove_index(name, column_name, **options)
737
836
  end
738
837
 
@@ -761,6 +860,7 @@ module ActiveRecord
761
860
  #
762
861
  # See {connection.add_reference}[rdoc-ref:SchemaStatements#add_reference] for details of the options you can use.
763
862
  def references(*args, **options)
863
+ raise_on_if_exist_options(options)
764
864
  args.each do |ref_name|
765
865
  @base.add_reference(name, ref_name, **options)
766
866
  end
@@ -774,6 +874,7 @@ module ActiveRecord
774
874
  #
775
875
  # See {connection.remove_reference}[rdoc-ref:SchemaStatements#remove_reference]
776
876
  def remove_references(*args, **options)
877
+ raise_on_if_exist_options(options)
777
878
  args.each do |ref_name|
778
879
  @base.remove_reference(name, ref_name, **options)
779
880
  end
@@ -787,6 +888,7 @@ module ActiveRecord
787
888
  #
788
889
  # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
789
890
  def foreign_key(*args, **options)
891
+ raise_on_if_exist_options(options)
790
892
  @base.add_foreign_key(name, *args, **options)
791
893
  end
792
894
 
@@ -797,6 +899,7 @@ module ActiveRecord
797
899
  #
798
900
  # See {connection.remove_foreign_key}[rdoc-ref:SchemaStatements#remove_foreign_key]
799
901
  def remove_foreign_key(*args, **options)
902
+ raise_on_if_exist_options(options)
800
903
  @base.remove_foreign_key(name, *args, **options)
801
904
  end
802
905
 
@@ -826,6 +929,33 @@ module ActiveRecord
826
929
  def remove_check_constraint(*args, **options)
827
930
  @base.remove_check_constraint(name, *args, **options)
828
931
  end
932
+
933
+ # Checks if a check_constraint exists on a table.
934
+ #
935
+ # unless t.check_constraint_exists?(name: "price_check")
936
+ # t.check_constraint("price > 0", name: "price_check")
937
+ # end
938
+ #
939
+ # See {connection.check_constraint_exists?}[rdoc-ref:SchemaStatements#check_constraint_exists?]
940
+ def check_constraint_exists?(*args, **options)
941
+ @base.check_constraint_exists?(name, *args, **options)
942
+ end
943
+
944
+ private
945
+ def raise_on_if_exist_options(options)
946
+ unrecognized_option = options.keys.find do |key|
947
+ key == :if_exists || key == :if_not_exists
948
+ end
949
+ if unrecognized_option
950
+ conditional = unrecognized_option == :if_exists ? "if" : "unless"
951
+ message = <<~TXT
952
+ Option #{unrecognized_option} will be ignored. If you are calling an expression like
953
+ `t.column(.., #{unrecognized_option}: true)` from inside a change_table block, try a
954
+ conditional clause instead, as in `t.column(..) #{conditional} t.column_exists?(..)`
955
+ TXT
956
+ raise ArgumentError.new(message)
957
+ end
958
+ end
829
959
  end
830
960
  end
831
961
  end