activerecord 7.1.5.1 → 8.0.2

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -4,9 +4,62 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module Quoting
7
+ extend ActiveSupport::Concern
8
+
7
9
  QUOTED_COLUMN_NAMES = Concurrent::Map.new # :nodoc:
8
10
  QUOTED_TABLE_NAMES = Concurrent::Map.new # :nodoc:
9
11
 
12
+ module ClassMethods # :nodoc:
13
+ def column_name_matcher
14
+ /
15
+ \A
16
+ (
17
+ (?:
18
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
19
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
20
+ )
21
+ (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
22
+ )
23
+ (?:\s*,\s*\g<1>)*
24
+ \z
25
+ /ix
26
+ end
27
+
28
+ def column_name_with_order_matcher
29
+ /
30
+ \A
31
+ (
32
+ (?:
33
+ # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
34
+ ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
35
+ )
36
+ (?:\s+COLLATE\s+"\w+")?
37
+ (?:\s+ASC|\s+DESC)?
38
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
39
+ )
40
+ (?:\s*,\s*\g<1>)*
41
+ \z
42
+ /ix
43
+ end
44
+
45
+ # Quotes column names for use in SQL queries.
46
+ def quote_column_name(name) # :nodoc:
47
+ QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(name.to_s).freeze
48
+ end
49
+
50
+ # Checks the following cases:
51
+ #
52
+ # - table_name
53
+ # - "table.name"
54
+ # - schema_name.table_name
55
+ # - schema_name."table.name"
56
+ # - "schema.name".table_name
57
+ # - "schema.name"."table.name"
58
+ def quote_table_name(name) # :nodoc:
59
+ QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
60
+ end
61
+ end
62
+
10
63
  class IntegerOutOf64BitRange < StandardError
11
64
  def initialize(msg)
12
65
  super(msg)
@@ -77,30 +130,13 @@ module ActiveRecord
77
130
  end
78
131
  end
79
132
 
80
- # Checks the following cases:
81
- #
82
- # - table_name
83
- # - "table.name"
84
- # - schema_name.table_name
85
- # - schema_name."table.name"
86
- # - "schema.name".table_name
87
- # - "schema.name"."table.name"
88
- def quote_table_name(name) # :nodoc:
89
- QUOTED_TABLE_NAMES[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
90
- end
91
-
92
- # Quotes schema names for use in SQL queries.
93
- def quote_schema_name(name)
94
- PG::Connection.quote_ident(name)
95
- end
96
-
97
133
  def quote_table_name_for_assignment(table, attr)
98
134
  quote_column_name(attr)
99
135
  end
100
136
 
101
- # Quotes column names for use in SQL queries.
102
- def quote_column_name(name) # :nodoc:
103
- QUOTED_COLUMN_NAMES[name] ||= PG::Connection.quote_ident(super).freeze
137
+ # Quotes schema names for use in SQL queries.
138
+ def quote_schema_name(schema_name)
139
+ quote_column_name(schema_name)
104
140
  end
105
141
 
106
142
  # Quote date/time values for use in SQL input.
@@ -143,6 +179,8 @@ module ActiveRecord
143
179
  encode_array(value)
144
180
  when Range
145
181
  encode_range(value)
182
+ when Rational
183
+ value.to_f
146
184
  else
147
185
  super
148
186
  end
@@ -153,44 +191,6 @@ module ActiveRecord
153
191
  type_map.lookup(column.oid, column.fmod, column.sql_type)
154
192
  end
155
193
 
156
- def column_name_matcher
157
- COLUMN_NAME
158
- end
159
-
160
- def column_name_with_order_matcher
161
- COLUMN_NAME_WITH_ORDER
162
- end
163
-
164
- COLUMN_NAME = /
165
- \A
166
- (
167
- (?:
168
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
169
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
170
- )
171
- (?:(?:\s+AS)?\s+(?:\w+|"\w+"))?
172
- )
173
- (?:\s*,\s*\g<1>)*
174
- \z
175
- /ix
176
-
177
- COLUMN_NAME_WITH_ORDER = /
178
- \A
179
- (
180
- (?:
181
- # "schema_name"."table_name"."column_name"::type_name | function(one or no argument)::type_name
182
- ((?:\w+\.|"\w+"\.){,2}(?:\w+|"\w+")(?:::\w+)? | \w+\((?:|\g<2>)\)(?:::\w+)?)
183
- )
184
- (?:\s+COLLATE\s+"\w+")?
185
- (?:\s+ASC|\s+DESC)?
186
- (?:\s+NULLS\s+(?:FIRST|LAST))?
187
- )
188
- (?:\s*,\s*\g<1>)*
189
- \z
190
- /ix
191
-
192
- private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
193
-
194
194
  private
195
195
  def lookup_cast_type(sql_type)
196
196
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
@@ -45,12 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
49
49
  constraint_name,
50
50
  table_schema,
51
- table_schema,
52
- table_name,
53
- constraint_name
51
+ table_name
54
52
  ) AS constraint_check
55
53
  FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
56
54
  )
@@ -11,14 +11,11 @@ module ActiveRecord
11
11
  sql = super
12
12
  sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
13
13
  sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
14
- sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
15
14
  sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
16
- sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
17
15
  end
18
16
 
19
17
  def visit_AddForeignKey(o)
20
18
  super.dup.tap do |sql|
21
- sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
22
19
  sql << " NOT VALID" unless o.validate?
23
20
  end
24
21
  end
@@ -55,6 +52,7 @@ module ActiveRecord
55
52
  sql = ["CONSTRAINT"]
56
53
  sql << quote_column_name(o.name)
57
54
  sql << "UNIQUE"
55
+ sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
58
56
 
59
57
  if o.using_index
60
58
  sql << "USING INDEX #{quote_column_name(o.using_index)}"
@@ -73,18 +71,10 @@ module ActiveRecord
73
71
  "ADD #{accept(o)}"
74
72
  end
75
73
 
76
- def visit_DropExclusionConstraint(name)
77
- "DROP CONSTRAINT #{quote_column_name(name)}"
78
- end
79
-
80
74
  def visit_AddUniqueConstraint(o)
81
75
  "ADD #{accept(o)}"
82
76
  end
83
77
 
84
- def visit_DropUniqueConstraint(name)
85
- "DROP CONSTRAINT #{quote_column_name(name)}"
86
- end
87
-
88
78
  def visit_ChangeColumnDefinition(o)
89
79
  column = o.column
90
80
  column.sql_type = type_to_sql(column.type, **column.options)
@@ -224,11 +224,17 @@ module ActiveRecord
224
224
  options[:using_index]
225
225
  end
226
226
 
227
+ def nulls_not_distinct
228
+ options[:nulls_not_distinct]
229
+ end
230
+
227
231
  def export_name_on_schema_dump?
228
232
  !ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
229
233
  end
230
234
 
231
235
  def defined_for?(name: nil, column: nil, **options)
236
+ options = options.slice(*self.options.keys)
237
+
232
238
  (name.nil? || self.name == name.to_s) &&
233
239
  (column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
234
240
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -302,8 +308,8 @@ module ActiveRecord
302
308
  # t.exclusion_constraint("price WITH =, availability_range WITH &&", using: :gist, name: "price_check")
303
309
  #
304
310
  # See {connection.add_exclusion_constraint}[rdoc-ref:SchemaStatements#add_exclusion_constraint]
305
- def exclusion_constraint(*args)
306
- @base.add_exclusion_constraint(name, *args)
311
+ def exclusion_constraint(...)
312
+ @base.add_exclusion_constraint(name, ...)
307
313
  end
308
314
 
309
315
  # Removes the given exclusion constraint from the table.
@@ -311,17 +317,17 @@ module ActiveRecord
311
317
  # t.remove_exclusion_constraint(name: "price_check")
312
318
  #
313
319
  # See {connection.remove_exclusion_constraint}[rdoc-ref:SchemaStatements#remove_exclusion_constraint]
314
- def remove_exclusion_constraint(*args)
315
- @base.remove_exclusion_constraint(name, *args)
320
+ def remove_exclusion_constraint(...)
321
+ @base.remove_exclusion_constraint(name, ...)
316
322
  end
317
323
 
318
324
  # Adds a unique constraint.
319
325
  #
320
- # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
326
+ # t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
321
327
  #
322
328
  # See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
323
- def unique_constraint(*args)
324
- @base.add_unique_constraint(name, *args)
329
+ def unique_constraint(...)
330
+ @base.add_unique_constraint(name, ...)
325
331
  end
326
332
 
327
333
  # Removes the given unique constraint from the table.
@@ -329,22 +335,40 @@ module ActiveRecord
329
335
  # t.remove_unique_constraint(name: "unique_position")
330
336
  #
331
337
  # See {connection.remove_unique_constraint}[rdoc-ref:SchemaStatements#remove_unique_constraint]
332
- def remove_unique_constraint(*args)
333
- @base.remove_unique_constraint(name, *args)
338
+ def remove_unique_constraint(...)
339
+ @base.remove_unique_constraint(name, ...)
340
+ end
341
+
342
+ # Validates the given constraint on the table.
343
+ #
344
+ # t.check_constraint("price > 0", name: "price_check", validate: false)
345
+ # t.validate_constraint "price_check"
346
+ #
347
+ # See {connection.validate_constraint}[rdoc-ref:SchemaStatements#validate_constraint]
348
+ def validate_constraint(...)
349
+ @base.validate_constraint(name, ...)
350
+ end
351
+
352
+ # Validates the given check constraint on the table
353
+ #
354
+ # t.check_constraint("price > 0", name: "price_check", validate: false)
355
+ # t.validate_check_constraint name: "price_check"
356
+ #
357
+ # See {connection.validate_check_constraint}[rdoc-ref:SchemaStatements#validate_check_constraint]
358
+ def validate_check_constraint(...)
359
+ @base.validate_check_constraint(name, ...)
334
360
  end
335
361
  end
336
362
 
337
363
  # = Active Record PostgreSQL Adapter Alter \Table
338
364
  class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
339
- attr_reader :constraint_validations, :exclusion_constraint_adds, :exclusion_constraint_drops, :unique_constraint_adds, :unique_constraint_drops
365
+ attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
340
366
 
341
367
  def initialize(td)
342
368
  super
343
369
  @constraint_validations = []
344
370
  @exclusion_constraint_adds = []
345
- @exclusion_constraint_drops = []
346
371
  @unique_constraint_adds = []
347
- @unique_constraint_drops = []
348
372
  end
349
373
 
350
374
  def validate_constraint(name)
@@ -355,17 +379,9 @@ module ActiveRecord
355
379
  @exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
356
380
  end
357
381
 
358
- def drop_exclusion_constraint(constraint_name)
359
- @exclusion_constraint_drops << constraint_name
360
- end
361
-
362
382
  def add_unique_constraint(column_name, options)
363
383
  @unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
364
384
  end
365
-
366
- def drop_unique_constraint(unique_constraint_name)
367
- @unique_constraint_drops << unique_constraint_name
368
- end
369
385
  end
370
386
  end
371
387
  end
@@ -22,7 +22,7 @@ module ActiveRecord
22
22
  stream.puts " # Custom types defined in this database."
23
23
  stream.puts " # Note that some types may not work with other database engines. Be careful if changing database."
24
24
  types.sort.each do |name, values|
25
- stream.puts " create_enum #{name.inspect}, #{values.split(",").inspect}"
25
+ stream.puts " create_enum #{name.inspect}, #{values.inspect}"
26
26
  end
27
27
  stream.puts
28
28
  end
@@ -68,6 +68,7 @@ module ActiveRecord
68
68
  "t.unique_constraint #{unique_constraint.column.inspect}"
69
69
  ]
70
70
 
71
+ parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
71
72
  parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
72
73
 
73
74
  if unique_constraint.export_name_on_schema_dump?
@@ -91,7 +92,7 @@ module ActiveRecord
91
92
  spec = { type: schema_type(column).inspect }.merge!(spec)
92
93
  end
93
94
 
94
- spec[:enum_type] = "\"#{column.sql_type}\"" if column.enum?
95
+ spec[:enum_type] = column.sql_type.inspect if column.enum?
95
96
 
96
97
  spec
97
98
  end
@@ -54,9 +54,9 @@ module ActiveRecord
54
54
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
55
  end
56
56
 
57
- def drop_table(table_name, **options) # :nodoc:
58
- schema_cache.clear_data_source_cache!(table_name.to_s)
59
- execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
57
+ def drop_table(*table_names, **options) # :nodoc:
58
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
59
+ execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{table_names.map { |table_name| quote_table_name(table_name) }.join(', ')}#{' CASCADE' if options[:force] == :cascade}"
60
60
  end
61
61
 
62
62
  # Returns true if schema exists.
@@ -112,7 +112,7 @@ module ActiveRecord
112
112
 
113
113
  orders = {}
114
114
  opclasses = {}
115
- include_columns = include ? include.split(",").map(&:strip) : []
115
+ include_columns = include ? include.split(",").map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : []
116
116
 
117
117
  if indkey.include?(0)
118
118
  columns = expressions
@@ -152,9 +152,23 @@ module ActiveRecord
152
152
  end
153
153
 
154
154
  def table_options(table_name) # :nodoc:
155
- if comment = table_comment(table_name)
156
- { comment: comment }
155
+ options = {}
156
+
157
+ comment = table_comment(table_name)
158
+
159
+ options[:comment] = comment if comment
160
+
161
+ inherited_table_names = inherited_table_names(table_name).presence
162
+
163
+ options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names
164
+
165
+ if !options[:options] && supports_native_partitioning?
166
+ partition_definition = table_partition_definition(table_name)
167
+
168
+ options[:options] = "PARTITION BY #{partition_definition}" if partition_definition
157
169
  end
170
+
171
+ options
158
172
  end
159
173
 
160
174
  # Returns a comment stored in database for given table
@@ -172,6 +186,36 @@ module ActiveRecord
172
186
  end
173
187
  end
174
188
 
189
+ # Returns the partition definition of a given table
190
+ def table_partition_definition(table_name) # :nodoc:
191
+ scope = quoted_scope(table_name, type: "BASE TABLE")
192
+
193
+ query_value(<<~SQL, "SCHEMA")
194
+ SELECT pg_catalog.pg_get_partkeydef(c.oid)
195
+ FROM pg_catalog.pg_class c
196
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
197
+ WHERE c.relname = #{scope[:name]}
198
+ AND c.relkind IN (#{scope[:type]})
199
+ AND n.nspname = #{scope[:schema]}
200
+ SQL
201
+ end
202
+
203
+ # Returns the inherited table name of a given table
204
+ def inherited_table_names(table_name) # :nodoc:
205
+ scope = quoted_scope(table_name, type: "BASE TABLE")
206
+
207
+ query_values(<<~SQL, "SCHEMA")
208
+ SELECT parent.relname
209
+ FROM pg_catalog.pg_inherits i
210
+ JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid
211
+ JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid
212
+ LEFT JOIN pg_namespace n ON n.oid = child.relnamespace
213
+ WHERE child.relname = #{scope[:name]}
214
+ AND child.relkind IN (#{scope[:type]})
215
+ AND n.nspname = #{scope[:schema]}
216
+ SQL
217
+ end
218
+
175
219
  # Returns the current database name.
176
220
  def current_database
177
221
  query_value("SELECT current_database()", "SCHEMA")
@@ -209,8 +253,16 @@ module ActiveRecord
209
253
  end
210
254
 
211
255
  # Creates a schema for the given schema name.
212
- def create_schema(schema_name)
213
- execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
256
+ def create_schema(schema_name, force: nil, if_not_exists: nil)
257
+ if force && if_not_exists
258
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
259
+ end
260
+
261
+ if force
262
+ drop_schema(schema_name, if_exists: true)
263
+ end
264
+
265
+ execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}")
214
266
  end
215
267
 
216
268
  # Drops the schema for the given schema name.
@@ -242,7 +294,7 @@ module ActiveRecord
242
294
 
243
295
  # Set the client message level.
244
296
  def client_min_messages=(level)
245
- internal_execute("SET client_min_messages TO '#{level}'")
297
+ internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
246
298
  end
247
299
 
248
300
  # Returns the sequence name for a table's primary key or some other specified key.
@@ -524,14 +576,6 @@ module ActiveRecord
524
576
  end
525
577
 
526
578
  def add_foreign_key(from_table, to_table, **options)
527
- if options[:deferrable] == true
528
- ActiveRecord.deprecator.warn(<<~MSG)
529
- `deferrable: true` is deprecated in favor of `deferrable: :immediate`, and will be removed in Rails 7.2.
530
- MSG
531
-
532
- options[:deferrable] = :immediate
533
- end
534
-
535
579
  assert_valid_deferrable(options[:deferrable])
536
580
 
537
581
  super
@@ -654,7 +698,7 @@ module ActiveRecord
654
698
  scope = quoted_scope(table_name)
655
699
 
656
700
  unique_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
657
- SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred
701
+ SELECT c.conname, c.conrelid, c.conkey, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef
658
702
  FROM pg_constraint c
659
703
  JOIN pg_class t ON c.conrelid = t.oid
660
704
  JOIN pg_namespace n ON n.oid = c.connamespace
@@ -667,10 +711,12 @@ module ActiveRecord
667
711
  conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
668
712
  columns = column_names_from_column_numbers(row["conrelid"], conkey)
669
713
 
714
+ nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
670
715
  deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
671
716
 
672
717
  options = {
673
718
  name: row["conname"],
719
+ nulls_not_distinct: nulls_not_distinct,
674
720
  deferrable: deferrable
675
721
  }
676
722
 
@@ -692,6 +738,10 @@ module ActiveRecord
692
738
  # The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
693
739
  # [<tt>:deferrable</tt>]
694
740
  # Specify whether or not the exclusion constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
741
+ # [<tt>:using</tt>]
742
+ # Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
743
+ # [<tt>:where</tt>]
744
+ # Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
695
745
  def add_exclusion_constraint(table_name, expression, **options)
696
746
  options = exclusion_constraint_options(table_name, expression, options)
697
747
  at = create_alter_table(table_name)
@@ -718,15 +768,12 @@ module ActiveRecord
718
768
  def remove_exclusion_constraint(table_name, expression = nil, **options)
719
769
  excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
720
770
 
721
- at = create_alter_table(table_name)
722
- at.drop_exclusion_constraint(excl_name_to_delete)
723
-
724
- execute schema_creation.accept(at)
771
+ remove_constraint(table_name, excl_name_to_delete)
725
772
  end
726
773
 
727
774
  # Adds a new unique constraint to the table.
728
775
  #
729
- # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
776
+ # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true
730
777
  #
731
778
  # generates:
732
779
  #
@@ -743,6 +790,9 @@ module ActiveRecord
743
790
  # Specify whether or not the unique constraint should be deferrable. Valid values are +false+ or +:immediate+ or +:deferred+ to specify the default behavior. Defaults to +false+.
744
791
  # [<tt>:using_index</tt>]
745
792
  # To specify an existing unique index name. Defaults to +nil+.
793
+ # [<tt>:nulls_not_distinct</tt>]
794
+ # Create a unique constraint where NULLs are treated equally.
795
+ # Note: only supported by PostgreSQL version 15.0.0 and greater.
746
796
  def add_unique_constraint(table_name, column_name = nil, **options)
747
797
  options = unique_constraint_options(table_name, column_name, options)
748
798
  at = create_alter_table(table_name)
@@ -773,10 +823,7 @@ module ActiveRecord
773
823
  def remove_unique_constraint(table_name, column_name = nil, **options)
774
824
  unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
775
825
 
776
- at = create_alter_table(table_name)
777
- at.drop_unique_constraint(unique_name_to_delete)
778
-
779
- execute schema_creation.accept(at)
826
+ remove_constraint(table_name, unique_name_to_delete)
780
827
  end
781
828
 
782
829
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -875,7 +922,7 @@ module ActiveRecord
875
922
  #
876
923
  # validate_check_constraint :products, name: "price_check"
877
924
  #
878
- # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
925
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
879
926
  def validate_check_constraint(table_name, **options)
880
927
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
881
928