activerecord 7.2.2.1 → 8.1.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 +564 -753
  3. data/README.rdoc +2 -2
  4. data/lib/active_record/association_relation.rb +2 -1
  5. data/lib/active_record/associations/alias_tracker.rb +6 -4
  6. data/lib/active_record/associations/association.rb +35 -11
  7. data/lib/active_record/associations/belongs_to_association.rb +18 -2
  8. data/lib/active_record/associations/builder/association.rb +23 -11
  9. data/lib/active_record/associations/builder/belongs_to.rb +17 -4
  10. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  11. data/lib/active_record/associations/builder/has_one.rb +1 -1
  12. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  13. data/lib/active_record/associations/collection_association.rb +10 -8
  14. data/lib/active_record/associations/collection_proxy.rb +22 -4
  15. data/lib/active_record/associations/deprecation.rb +88 -0
  16. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  17. data/lib/active_record/associations/errors.rb +3 -0
  18. data/lib/active_record/associations/has_many_through_association.rb +3 -2
  19. data/lib/active_record/associations/join_dependency/join_association.rb +25 -27
  20. data/lib/active_record/associations/join_dependency.rb +4 -2
  21. data/lib/active_record/associations/preloader/association.rb +2 -2
  22. data/lib/active_record/associations/preloader/batch.rb +7 -1
  23. data/lib/active_record/associations/preloader/branch.rb +1 -0
  24. data/lib/active_record/associations/singular_association.rb +8 -3
  25. data/lib/active_record/associations.rb +192 -24
  26. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  27. data/lib/active_record/attribute_methods/primary_key.rb +4 -8
  28. data/lib/active_record/attribute_methods/query.rb +34 -0
  29. data/lib/active_record/attribute_methods/serialization.rb +17 -4
  30. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
  31. data/lib/active_record/attribute_methods.rb +24 -19
  32. data/lib/active_record/attributes.rb +40 -26
  33. data/lib/active_record/autosave_association.rb +91 -39
  34. data/lib/active_record/base.rb +3 -4
  35. data/lib/active_record/coders/json.rb +14 -5
  36. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
  37. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  38. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
  39. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +458 -117
  40. data/lib/active_record/connection_adapters/abstract/database_statements.rb +136 -74
  41. data/lib/active_record/connection_adapters/abstract/query_cache.rb +44 -11
  42. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
  43. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  44. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  45. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  46. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -29
  47. data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
  48. data/lib/active_record/connection_adapters/abstract_adapter.rb +175 -87
  49. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +77 -58
  50. data/lib/active_record/connection_adapters/column.rb +17 -4
  51. data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
  52. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -9
  53. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  54. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  55. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
  56. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
  57. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -11
  58. data/lib/active_record/connection_adapters/pool_config.rb +7 -7
  59. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  60. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  62. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
  65. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  66. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  67. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +28 -45
  68. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  69. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +140 -64
  70. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -105
  71. data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
  72. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
  73. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -8
  74. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
  75. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
  76. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +13 -13
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +112 -42
  78. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  79. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
  80. data/lib/active_record/connection_adapters/trilogy_adapter.rb +2 -19
  81. data/lib/active_record/connection_adapters.rb +1 -56
  82. data/lib/active_record/connection_handling.rb +37 -10
  83. data/lib/active_record/core.rb +61 -25
  84. data/lib/active_record/counter_cache.rb +34 -9
  85. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  86. data/lib/active_record/database_configurations/database_config.rb +9 -1
  87. data/lib/active_record/database_configurations/hash_config.rb +67 -9
  88. data/lib/active_record/database_configurations/url_config.rb +13 -3
  89. data/lib/active_record/database_configurations.rb +7 -3
  90. data/lib/active_record/delegated_type.rb +19 -19
  91. data/lib/active_record/dynamic_matchers.rb +54 -69
  92. data/lib/active_record/encryption/config.rb +3 -1
  93. data/lib/active_record/encryption/encryptable_record.rb +9 -9
  94. data/lib/active_record/encryption/encrypted_attribute_type.rb +12 -3
  95. data/lib/active_record/encryption/encryptor.rb +49 -28
  96. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  97. data/lib/active_record/encryption/scheme.rb +9 -2
  98. data/lib/active_record/enum.rb +46 -42
  99. data/lib/active_record/errors.rb +36 -12
  100. data/lib/active_record/explain.rb +1 -1
  101. data/lib/active_record/explain_registry.rb +51 -2
  102. data/lib/active_record/filter_attribute_handler.rb +73 -0
  103. data/lib/active_record/fixture_set/table_row.rb +19 -2
  104. data/lib/active_record/fixtures.rb +2 -4
  105. data/lib/active_record/future_result.rb +13 -9
  106. data/lib/active_record/gem_version.rb +3 -3
  107. data/lib/active_record/inheritance.rb +1 -1
  108. data/lib/active_record/insert_all.rb +12 -7
  109. data/lib/active_record/locking/optimistic.rb +8 -1
  110. data/lib/active_record/locking/pessimistic.rb +5 -0
  111. data/lib/active_record/log_subscriber.rb +3 -13
  112. data/lib/active_record/middleware/shard_selector.rb +34 -17
  113. data/lib/active_record/migration/command_recorder.rb +44 -11
  114. data/lib/active_record/migration/compatibility.rb +37 -24
  115. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  116. data/lib/active_record/migration.rb +50 -43
  117. data/lib/active_record/model_schema.rb +38 -13
  118. data/lib/active_record/nested_attributes.rb +6 -6
  119. data/lib/active_record/persistence.rb +162 -133
  120. data/lib/active_record/query_cache.rb +22 -15
  121. data/lib/active_record/query_logs.rb +104 -52
  122. data/lib/active_record/query_logs_formatter.rb +17 -28
  123. data/lib/active_record/querying.rb +12 -12
  124. data/lib/active_record/railtie.rb +37 -32
  125. data/lib/active_record/railties/controller_runtime.rb +11 -6
  126. data/lib/active_record/railties/databases.rake +26 -37
  127. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  128. data/lib/active_record/railties/job_runtime.rb +10 -11
  129. data/lib/active_record/reflection.rb +53 -21
  130. data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
  131. data/lib/active_record/relation/batches.rb +147 -73
  132. data/lib/active_record/relation/calculations.rb +80 -63
  133. data/lib/active_record/relation/delegation.rb +25 -15
  134. data/lib/active_record/relation/finder_methods.rb +54 -37
  135. data/lib/active_record/relation/merger.rb +8 -8
  136. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -9
  137. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  138. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  139. data/lib/active_record/relation/predicate_builder.rb +22 -7
  140. data/lib/active_record/relation/query_attribute.rb +4 -2
  141. data/lib/active_record/relation/query_methods.rb +156 -95
  142. data/lib/active_record/relation/spawn_methods.rb +7 -7
  143. data/lib/active_record/relation/where_clause.rb +10 -11
  144. data/lib/active_record/relation.rb +122 -80
  145. data/lib/active_record/result.rb +109 -24
  146. data/lib/active_record/runtime_registry.rb +42 -58
  147. data/lib/active_record/sanitization.rb +9 -6
  148. data/lib/active_record/schema_dumper.rb +47 -22
  149. data/lib/active_record/schema_migration.rb +2 -1
  150. data/lib/active_record/scoping/named.rb +5 -2
  151. data/lib/active_record/scoping.rb +0 -1
  152. data/lib/active_record/secure_token.rb +3 -3
  153. data/lib/active_record/signed_id.rb +47 -18
  154. data/lib/active_record/statement_cache.rb +24 -20
  155. data/lib/active_record/store.rb +51 -22
  156. data/lib/active_record/structured_event_subscriber.rb +85 -0
  157. data/lib/active_record/table_metadata.rb +6 -23
  158. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  159. data/lib/active_record/tasks/database_tasks.rb +85 -85
  160. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  161. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  162. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
  163. data/lib/active_record/test_databases.rb +14 -4
  164. data/lib/active_record/test_fixtures.rb +39 -2
  165. data/lib/active_record/testing/query_assertions.rb +8 -2
  166. data/lib/active_record/timestamp.rb +4 -2
  167. data/lib/active_record/token_for.rb +1 -1
  168. data/lib/active_record/transaction.rb +2 -5
  169. data/lib/active_record/transactions.rb +39 -16
  170. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  171. data/lib/active_record/type/internal/timezone.rb +7 -0
  172. data/lib/active_record/type/json.rb +15 -2
  173. data/lib/active_record/type/serialized.rb +11 -4
  174. data/lib/active_record/type/type_map.rb +1 -1
  175. data/lib/active_record/type_caster/connection.rb +2 -1
  176. data/lib/active_record/validations/associated.rb +1 -1
  177. data/lib/active_record/validations/uniqueness.rb +8 -8
  178. data/lib/active_record.rb +85 -50
  179. data/lib/arel/alias_predication.rb +2 -0
  180. data/lib/arel/collectors/bind.rb +2 -2
  181. data/lib/arel/collectors/sql_string.rb +1 -1
  182. data/lib/arel/collectors/substitute_binds.rb +2 -2
  183. data/lib/arel/crud.rb +8 -11
  184. data/lib/arel/delete_manager.rb +5 -0
  185. data/lib/arel/nodes/binary.rb +1 -1
  186. data/lib/arel/nodes/count.rb +2 -2
  187. data/lib/arel/nodes/delete_statement.rb +4 -2
  188. data/lib/arel/nodes/function.rb +4 -10
  189. data/lib/arel/nodes/named_function.rb +2 -2
  190. data/lib/arel/nodes/node.rb +2 -2
  191. data/lib/arel/nodes/sql_literal.rb +1 -1
  192. data/lib/arel/nodes/update_statement.rb +4 -2
  193. data/lib/arel/nodes.rb +0 -2
  194. data/lib/arel/select_manager.rb +13 -4
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/update_manager.rb +5 -0
  197. data/lib/arel/visitors/dot.rb +2 -3
  198. data/lib/arel/visitors/postgresql.rb +55 -0
  199. data/lib/arel/visitors/sqlite.rb +55 -8
  200. data/lib/arel/visitors/to_sql.rb +6 -22
  201. data/lib/arel.rb +3 -1
  202. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  203. metadata +17 -17
  204. data/lib/active_record/explain_subscriber.rb +0 -34
  205. data/lib/active_record/normalization.rb +0 -163
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -52
@@ -12,9 +12,10 @@ module ActiveRecord
12
12
  end
13
13
 
14
14
  # Create a new PostgreSQL database. Options include <tt>:owner</tt>, <tt>:template</tt>,
15
- # <tt>:encoding</tt> (defaults to utf8), <tt>:collation</tt>, <tt>:ctype</tt>,
16
- # <tt>:tablespace</tt>, and <tt>:connection_limit</tt> (note that MySQL uses
17
- # <tt>:charset</tt> while PostgreSQL uses <tt>:encoding</tt>).
15
+ # <tt>:encoding</tt> (defaults to utf8), <tt>:locale_provider</tt>, <tt>:locale</tt>,
16
+ # <tt>:collation</tt>, <tt>:ctype</tt>, <tt>:tablespace</tt>, and
17
+ # <tt>:connection_limit</tt> (note that MySQL uses <tt>:charset</tt> while PostgreSQL
18
+ # uses <tt>:encoding</tt>).
18
19
  #
19
20
  # Example:
20
21
  # create_database config[:database], config
@@ -30,6 +31,10 @@ module ActiveRecord
30
31
  " TEMPLATE = \"#{value}\""
31
32
  when :encoding
32
33
  " ENCODING = '#{value}'"
34
+ when :locale_provider
35
+ " LOCALE_PROVIDER = '#{value}'"
36
+ when :locale
37
+ " LOCALE = '#{value}'"
33
38
  when :collation
34
39
  " LC_COLLATE = '#{value}'"
35
40
  when :ctype
@@ -54,9 +59,9 @@ module ActiveRecord
54
59
  execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
55
60
  end
56
61
 
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}"
62
+ def drop_table(*table_names, **options) # :nodoc:
63
+ table_names.each { |table_name| schema_cache.clear_data_source_cache!(table_name.to_s) }
64
+ 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
65
  end
61
66
 
62
67
  # Returns true if schema exists.
@@ -87,8 +92,13 @@ module ActiveRecord
87
92
  scope = quoted_scope(table_name)
88
93
 
89
94
  result = query(<<~SQL, "SCHEMA")
90
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
91
- pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid
95
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid),
96
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment, d.indisvalid,
97
+ ARRAY(
98
+ SELECT pg_get_indexdef(d.indexrelid, k + 1, true)
99
+ FROM generate_subscripts(d.indkey, 1) AS k
100
+ ORDER BY k
101
+ ) AS columns
92
102
  FROM pg_class t
93
103
  INNER JOIN pg_index d ON t.oid = d.indrelid
94
104
  INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -105,9 +115,10 @@ module ActiveRecord
105
115
  unique = row[1]
106
116
  indkey = row[2].split(" ").map(&:to_i)
107
117
  inddef = row[3]
108
- oid = row[4]
109
- comment = row[5]
110
- valid = row[6]
118
+ comment = row[4]
119
+ valid = row[5]
120
+ columns = decode_string_array(row[6]).map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) }
121
+
111
122
  using, expressions, include, nulls_not_distinct, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: INCLUDE \((.+?)\))?( NULLS NOT DISTINCT)?(?: WHERE (.+))?\z/m).flatten
112
123
 
113
124
  orders = {}
@@ -117,8 +128,6 @@ module ActiveRecord
117
128
  if indkey.include?(0)
118
129
  columns = expressions
119
130
  else
120
- columns = column_names_from_column_numbers(oid, indkey)
121
-
122
131
  # prevent INCLUDE columns from being matched
123
132
  columns.reject! { |c| include_columns.include?(c) }
124
133
 
@@ -152,9 +161,23 @@ module ActiveRecord
152
161
  end
153
162
 
154
163
  def table_options(table_name) # :nodoc:
155
- if comment = table_comment(table_name)
156
- { comment: comment }
164
+ options = {}
165
+
166
+ comment = table_comment(table_name)
167
+
168
+ options[:comment] = comment if comment
169
+
170
+ inherited_table_names = inherited_table_names(table_name).presence
171
+
172
+ options[:options] = "INHERITS (#{inherited_table_names.join(", ")})" if inherited_table_names
173
+
174
+ if !options[:options] && supports_native_partitioning?
175
+ partition_definition = table_partition_definition(table_name)
176
+
177
+ options[:options] = "PARTITION BY #{partition_definition}" if partition_definition
157
178
  end
179
+
180
+ options
158
181
  end
159
182
 
160
183
  # Returns a comment stored in database for given table
@@ -172,6 +195,36 @@ module ActiveRecord
172
195
  end
173
196
  end
174
197
 
198
+ # Returns the partition definition of a given table
199
+ def table_partition_definition(table_name) # :nodoc:
200
+ scope = quoted_scope(table_name, type: "BASE TABLE")
201
+
202
+ query_value(<<~SQL, "SCHEMA")
203
+ SELECT pg_catalog.pg_get_partkeydef(c.oid)
204
+ FROM pg_catalog.pg_class c
205
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
206
+ WHERE c.relname = #{scope[:name]}
207
+ AND c.relkind IN (#{scope[:type]})
208
+ AND n.nspname = #{scope[:schema]}
209
+ SQL
210
+ end
211
+
212
+ # Returns the inherited table name of a given table
213
+ def inherited_table_names(table_name) # :nodoc:
214
+ scope = quoted_scope(table_name, type: "BASE TABLE")
215
+
216
+ query_values(<<~SQL, "SCHEMA")
217
+ SELECT parent.relname
218
+ FROM pg_catalog.pg_inherits i
219
+ JOIN pg_catalog.pg_class child ON i.inhrelid = child.oid
220
+ JOIN pg_catalog.pg_class parent ON i.inhparent = parent.oid
221
+ LEFT JOIN pg_namespace n ON n.oid = child.relnamespace
222
+ WHERE child.relname = #{scope[:name]}
223
+ AND child.relkind IN (#{scope[:type]})
224
+ AND n.nspname = #{scope[:schema]}
225
+ SQL
226
+ end
227
+
175
228
  # Returns the current database name.
176
229
  def current_database
177
230
  query_value("SELECT current_database()", "SCHEMA")
@@ -182,6 +235,14 @@ module ActiveRecord
182
235
  query_value("SELECT current_schema", "SCHEMA")
183
236
  end
184
237
 
238
+ # Returns an array of the names of all schemas presently in the effective search path,
239
+ # in their priority order.
240
+ def current_schemas # :nodoc:
241
+ schemas = query_value("SELECT current_schemas(false)", "SCHEMA")
242
+ decoder = PG::TextDecoder::Array.new
243
+ decoder.decode(schemas)
244
+ end
245
+
185
246
  # Returns the current database encoding format.
186
247
  def encoding
187
248
  query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA")
@@ -226,12 +287,18 @@ module ActiveRecord
226
287
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
227
288
  end
228
289
 
290
+ # Renames the schema for the given schema name.
291
+ def rename_schema(schema_name, new_name)
292
+ execute "ALTER SCHEMA #{quote_schema_name(schema_name)} RENAME TO #{quote_schema_name(new_name)}"
293
+ end
294
+
229
295
  # Sets the schema search path to a string of comma-separated schema names.
230
296
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
231
297
  # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
232
298
  #
233
299
  # This should be not be called manually but set in database.yml.
234
300
  def schema_search_path=(schema_csv)
301
+ return if schema_csv == @schema_search_path
235
302
  if schema_csv
236
303
  internal_execute("SET search_path TO #{schema_csv}")
237
304
  @schema_search_path = schema_csv
@@ -250,7 +317,7 @@ module ActiveRecord
250
317
 
251
318
  # Set the client message level.
252
319
  def client_min_messages=(level)
253
- internal_execute("SET client_min_messages TO '#{level}'")
320
+ internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
254
321
  end
255
322
 
256
323
  # Returns the sequence name for a table's primary key or some other specified key.
@@ -276,7 +343,7 @@ module ActiveRecord
276
343
  if sequence
277
344
  quoted_sequence = quote_table_name(sequence)
278
345
 
279
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
346
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
280
347
  else
281
348
  @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
282
349
  end
@@ -307,7 +374,7 @@ module ActiveRecord
307
374
  end
308
375
  end
309
376
 
310
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
377
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{max_pk || minvalue}, #{max_pk ? true : false})", "SCHEMA")
311
378
  end
312
379
  end
313
380
 
@@ -368,16 +435,13 @@ module ActiveRecord
368
435
  def primary_keys(table_name) # :nodoc:
369
436
  query_values(<<~SQL, "SCHEMA")
370
437
  SELECT a.attname
371
- FROM (
372
- SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
373
- FROM pg_index
374
- WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
375
- AND indisprimary
376
- ) i
377
- JOIN pg_attribute a
378
- ON a.attrelid = i.indrelid
379
- AND a.attnum = i.indkey[i.idx]
380
- ORDER BY i.idx
438
+ FROM pg_index i
439
+ JOIN pg_attribute a
440
+ ON a.attrelid = i.indrelid
441
+ AND a.attnum = ANY(i.indkey)
442
+ WHERE i.indrelid = #{quote(quote_table_name(table_name))}::regclass
443
+ AND i.indisprimary
444
+ ORDER BY array_position(i.indkey, a.attnum)
381
445
  SQL
382
446
  end
383
447
 
@@ -540,36 +604,45 @@ module ActiveRecord
540
604
  def foreign_keys(table_name)
541
605
  scope = quoted_scope(table_name)
542
606
  fk_info = internal_exec_query(<<~SQL, "SCHEMA", allow_retry: true, materialize_transactions: false)
543
- SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conkey, c.confkey, c.conrelid, c.confrelid
607
+ SELECT t2.oid::regclass::text AS to_table, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid, c.condeferrable AS deferrable, c.condeferred AS deferred, c.conrelid, c.confrelid,
608
+ (
609
+ SELECT array_agg(a.attname ORDER BY idx)
610
+ FROM (
611
+ SELECT idx, c.conkey[idx] AS conkey_elem
612
+ FROM generate_subscripts(c.conkey, 1) AS idx
613
+ ) indexed_conkeys
614
+ JOIN pg_attribute a ON a.attrelid = t1.oid
615
+ AND a.attnum = indexed_conkeys.conkey_elem
616
+ ) AS conkey_names,
617
+ (
618
+ SELECT array_agg(a.attname ORDER BY idx)
619
+ FROM (
620
+ SELECT idx, c.confkey[idx] AS confkey_elem
621
+ FROM generate_subscripts(c.confkey, 1) AS idx
622
+ ) indexed_confkeys
623
+ JOIN pg_attribute a ON a.attrelid = t2.oid
624
+ AND a.attnum = indexed_confkeys.confkey_elem
625
+ ) AS confkey_names
544
626
  FROM pg_constraint c
545
627
  JOIN pg_class t1 ON c.conrelid = t1.oid
546
628
  JOIN pg_class t2 ON c.confrelid = t2.oid
547
- JOIN pg_attribute a1 ON a1.attnum = c.conkey[1] AND a1.attrelid = t1.oid
548
- JOIN pg_attribute a2 ON a2.attnum = c.confkey[1] AND a2.attrelid = t2.oid
549
- JOIN pg_namespace t3 ON c.connamespace = t3.oid
629
+ JOIN pg_namespace n ON c.connamespace = n.oid
550
630
  WHERE c.contype = 'f'
551
631
  AND t1.relname = #{scope[:name]}
552
- AND t3.nspname = #{scope[:schema]}
632
+ AND n.nspname = #{scope[:schema]}
553
633
  ORDER BY c.conname
554
634
  SQL
555
635
 
556
636
  fk_info.map do |row|
557
637
  to_table = Utils.unquote_identifier(row["to_table"])
558
- conkey = row["conkey"].scan(/\d+/).map(&:to_i)
559
- confkey = row["confkey"].scan(/\d+/).map(&:to_i)
560
638
 
561
- if conkey.size > 1
562
- column = column_names_from_column_numbers(row["conrelid"], conkey)
563
- primary_key = column_names_from_column_numbers(row["confrelid"], confkey)
564
- else
565
- column = Utils.unquote_identifier(row["column"])
566
- primary_key = row["primary_key"]
567
- end
639
+ column = decode_string_array(row["conkey_names"])
640
+ primary_key = decode_string_array(row["confkey_names"])
568
641
 
569
642
  options = {
570
- column: column,
643
+ column: column.size == 1 ? column.first : column,
571
644
  name: row["name"],
572
- primary_key: primary_key
645
+ primary_key: primary_key.size == 1 ? primary_key.first : primary_key
573
646
  }
574
647
 
575
648
  options[:on_delete] = extract_foreign_key_action(row["on_delete"])
@@ -654,7 +727,16 @@ module ActiveRecord
654
727
  scope = quoted_scope(table_name)
655
728
 
656
729
  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
730
+ SELECT c.conname, c.conrelid, c.condeferrable, c.condeferred, pg_get_constraintdef(c.oid) AS constraintdef,
731
+ (
732
+ SELECT array_agg(a.attname ORDER BY idx)
733
+ FROM (
734
+ SELECT idx, c.conkey[idx] AS conkey_elem
735
+ FROM generate_subscripts(c.conkey, 1) AS idx
736
+ ) indexed_conkeys
737
+ JOIN pg_attribute a ON a.attrelid = t.oid
738
+ AND a.attnum = indexed_conkeys.conkey_elem
739
+ ) AS conkey_names
658
740
  FROM pg_constraint c
659
741
  JOIN pg_class t ON c.conrelid = t.oid
660
742
  JOIN pg_namespace n ON n.oid = c.connamespace
@@ -664,13 +746,14 @@ module ActiveRecord
664
746
  SQL
665
747
 
666
748
  unique_info.map do |row|
667
- conkey = row["conkey"].delete("{}").split(",").map(&:to_i)
668
- columns = column_names_from_column_numbers(row["conrelid"], conkey)
749
+ columns = decode_string_array(row["conkey_names"])
669
750
 
751
+ nulls_not_distinct = row["constraintdef"].start_with?("UNIQUE NULLS NOT DISTINCT")
670
752
  deferrable = extract_constraint_deferrable(row["condeferrable"], row["condeferred"])
671
753
 
672
754
  options = {
673
755
  name: row["conname"],
756
+ nulls_not_distinct: nulls_not_distinct,
674
757
  deferrable: deferrable
675
758
  }
676
759
 
@@ -722,15 +805,12 @@ module ActiveRecord
722
805
  def remove_exclusion_constraint(table_name, expression = nil, **options)
723
806
  excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
724
807
 
725
- at = create_alter_table(table_name)
726
- at.drop_exclusion_constraint(excl_name_to_delete)
727
-
728
- execute schema_creation.accept(at)
808
+ remove_constraint(table_name, excl_name_to_delete)
729
809
  end
730
810
 
731
811
  # Adds a new unique constraint to the table.
732
812
  #
733
- # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position"
813
+ # add_unique_constraint :sections, [:position], deferrable: :deferred, name: "unique_position", nulls_not_distinct: true
734
814
  #
735
815
  # generates:
736
816
  #
@@ -747,6 +827,9 @@ module ActiveRecord
747
827
  # 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+.
748
828
  # [<tt>:using_index</tt>]
749
829
  # To specify an existing unique index name. Defaults to +nil+.
830
+ # [<tt>:nulls_not_distinct</tt>]
831
+ # Create a unique constraint where NULLs are treated equally.
832
+ # Note: only supported by PostgreSQL version 15.0.0 and greater.
750
833
  def add_unique_constraint(table_name, column_name = nil, **options)
751
834
  options = unique_constraint_options(table_name, column_name, options)
752
835
  at = create_alter_table(table_name)
@@ -777,10 +860,7 @@ module ActiveRecord
777
860
  def remove_unique_constraint(table_name, column_name = nil, **options)
778
861
  unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
779
862
 
780
- at = create_alter_table(table_name)
781
- at.drop_unique_constraint(unique_name_to_delete)
782
-
783
- execute schema_creation.accept(at)
863
+ remove_constraint(table_name, unique_name_to_delete)
784
864
  end
785
865
 
786
866
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -879,7 +959,7 @@ module ActiveRecord
879
959
  #
880
960
  # validate_check_constraint :products, name: "price_check"
881
961
  #
882
- # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
962
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
883
963
  def validate_check_constraint(table_name, **options)
884
964
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
885
965
 
@@ -937,6 +1017,7 @@ module ActiveRecord
937
1017
 
938
1018
  PostgreSQL::Column.new(
939
1019
  column_name,
1020
+ get_oid_type(oid.to_i, fmod.to_i, column_name, type),
940
1021
  default_value,
941
1022
  type_metadata,
942
1023
  !notnull,
@@ -1106,13 +1187,8 @@ module ActiveRecord
1106
1187
  [name.schema, name.identifier]
1107
1188
  end
1108
1189
 
1109
- def column_names_from_column_numbers(table_oid, column_numbers)
1110
- Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
1111
- SELECT a.attnum, a.attname
1112
- FROM pg_attribute a
1113
- WHERE a.attrelid = #{table_oid}
1114
- AND a.attnum IN (#{column_numbers.join(", ")})
1115
- SQL
1190
+ def decode_string_array(value)
1191
+ PG::TextDecoder::Array.new.decode(value)
1116
1192
  end
1117
1193
  end
1118
1194
  end