activerecord 7.1.5.2 → 8.1.2.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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +567 -2443
  3. data/README.rdoc +16 -16
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +3 -2
  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 +31 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +23 -11
  11. data/lib/active_record/associations/builder/belongs_to.rb +18 -4
  12. data/lib/active_record/associations/builder/collection_association.rb +7 -3
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  14. data/lib/active_record/associations/builder/has_many.rb +3 -4
  15. data/lib/active_record/associations/builder/has_one.rb +3 -4
  16. data/lib/active_record/associations/builder/singular_association.rb +33 -5
  17. data/lib/active_record/associations/collection_association.rb +17 -9
  18. data/lib/active_record/associations/collection_proxy.rb +36 -5
  19. data/lib/active_record/associations/deprecation.rb +88 -0
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  21. data/lib/active_record/associations/errors.rb +268 -0
  22. data/lib/active_record/associations/has_many_association.rb +1 -1
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  24. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +4 -2
  26. data/lib/active_record/associations/nested_error.rb +47 -0
  27. data/lib/active_record/associations/preloader/association.rb +4 -3
  28. data/lib/active_record/associations/preloader/batch.rb +7 -1
  29. data/lib/active_record/associations/preloader/branch.rb +8 -1
  30. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  31. data/lib/active_record/associations/singular_association.rb +14 -3
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +250 -315
  34. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  35. data/lib/active_record/attribute_assignment.rb +0 -2
  36. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  37. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  38. data/lib/active_record/attribute_methods/query.rb +34 -0
  39. data/lib/active_record/attribute_methods/read.rb +1 -13
  40. data/lib/active_record/attribute_methods/serialization.rb +20 -27
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
  42. data/lib/active_record/attribute_methods.rb +71 -75
  43. data/lib/active_record/attributes.rb +96 -68
  44. data/lib/active_record/autosave_association.rb +93 -58
  45. data/lib/active_record/base.rb +5 -7
  46. data/lib/active_record/callbacks.rb +1 -1
  47. data/lib/active_record/coders/json.rb +14 -5
  48. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
  49. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
  50. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
  52. data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
  53. data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
  54. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
  62. data/lib/active_record/connection_adapters/column.rb +17 -4
  63. data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
  64. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  65. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
  66. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
  68. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
  70. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
  74. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
  85. data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
  89. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
  92. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
  94. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  95. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
  96. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  97. data/lib/active_record/connection_adapters.rb +66 -0
  98. data/lib/active_record/connection_handling.rb +88 -46
  99. data/lib/active_record/core.rb +142 -58
  100. data/lib/active_record/counter_cache.rb +52 -18
  101. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  102. data/lib/active_record/database_configurations/database_config.rb +28 -5
  103. data/lib/active_record/database_configurations/hash_config.rb +103 -41
  104. data/lib/active_record/database_configurations/url_config.rb +30 -1
  105. data/lib/active_record/database_configurations.rb +8 -4
  106. data/lib/active_record/delegated_type.rb +43 -19
  107. data/lib/active_record/dynamic_matchers.rb +56 -71
  108. data/lib/active_record/encryption/config.rb +3 -1
  109. data/lib/active_record/encryption/encryptable_record.rb +12 -12
  110. data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
  111. data/lib/active_record/encryption/encryptor.rb +58 -22
  112. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  113. data/lib/active_record/encryption/key_provider.rb +1 -1
  114. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  115. data/lib/active_record/encryption/message_serializer.rb +4 -0
  116. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  117. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  118. data/lib/active_record/encryption/scheme.rb +9 -2
  119. data/lib/active_record/enum.rb +57 -36
  120. data/lib/active_record/errors.rb +76 -26
  121. data/lib/active_record/explain.rb +14 -25
  122. data/lib/active_record/explain_registry.rb +51 -2
  123. data/lib/active_record/filter_attribute_handler.rb +73 -0
  124. data/lib/active_record/fixture_set/table_row.rb +19 -2
  125. data/lib/active_record/fixtures.rb +39 -35
  126. data/lib/active_record/future_result.rb +21 -13
  127. data/lib/active_record/gem_version.rb +3 -3
  128. data/lib/active_record/inheritance.rb +5 -3
  129. data/lib/active_record/insert_all.rb +28 -20
  130. data/lib/active_record/integration.rb +4 -1
  131. data/lib/active_record/internal_metadata.rb +48 -34
  132. data/lib/active_record/locking/optimistic.rb +15 -7
  133. data/lib/active_record/locking/pessimistic.rb +5 -0
  134. data/lib/active_record/log_subscriber.rb +3 -34
  135. data/lib/active_record/message_pack.rb +1 -1
  136. data/lib/active_record/middleware/shard_selector.rb +34 -17
  137. data/lib/active_record/migration/command_recorder.rb +46 -14
  138. data/lib/active_record/migration/compatibility.rb +40 -25
  139. data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
  140. data/lib/active_record/migration/default_strategy.rb +4 -5
  141. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  142. data/lib/active_record/migration.rb +119 -103
  143. data/lib/active_record/model_schema.rb +66 -78
  144. data/lib/active_record/nested_attributes.rb +17 -9
  145. data/lib/active_record/persistence.rb +161 -454
  146. data/lib/active_record/query_cache.rb +33 -15
  147. data/lib/active_record/query_logs.rb +106 -39
  148. data/lib/active_record/query_logs_formatter.rb +17 -28
  149. data/lib/active_record/querying.rb +28 -16
  150. data/lib/active_record/railtie.rb +61 -74
  151. data/lib/active_record/railties/controller_runtime.rb +19 -5
  152. data/lib/active_record/railties/databases.rake +64 -78
  153. data/lib/active_record/railties/job_checkpoints.rb +15 -0
  154. data/lib/active_record/railties/job_runtime.rb +10 -11
  155. data/lib/active_record/reflection.rb +147 -53
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  157. data/lib/active_record/relation/batches.rb +153 -73
  158. data/lib/active_record/relation/calculations.rb +146 -96
  159. data/lib/active_record/relation/delegation.rb +30 -23
  160. data/lib/active_record/relation/finder_methods.rb +64 -34
  161. data/lib/active_record/relation/merger.rb +12 -14
  162. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  163. data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
  165. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  166. data/lib/active_record/relation/predicate_builder.rb +25 -10
  167. data/lib/active_record/relation/query_attribute.rb +4 -2
  168. data/lib/active_record/relation/query_methods.rb +351 -124
  169. data/lib/active_record/relation/spawn_methods.rb +9 -25
  170. data/lib/active_record/relation/where_clause.rb +17 -30
  171. data/lib/active_record/relation.rb +590 -124
  172. data/lib/active_record/result.rb +131 -59
  173. data/lib/active_record/runtime_registry.rb +44 -21
  174. data/lib/active_record/sanitization.rb +33 -25
  175. data/lib/active_record/schema.rb +8 -6
  176. data/lib/active_record/schema_dumper.rb +66 -31
  177. data/lib/active_record/schema_migration.rb +31 -14
  178. data/lib/active_record/scoping/named.rb +6 -2
  179. data/lib/active_record/scoping.rb +0 -1
  180. data/lib/active_record/secure_token.rb +3 -3
  181. data/lib/active_record/signed_id.rb +68 -20
  182. data/lib/active_record/statement_cache.rb +29 -25
  183. data/lib/active_record/store.rb +51 -22
  184. data/lib/active_record/structured_event_subscriber.rb +85 -0
  185. data/lib/active_record/table_metadata.rb +7 -33
  186. data/lib/active_record/tasks/abstract_tasks.rb +76 -0
  187. data/lib/active_record/tasks/database_tasks.rb +118 -90
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
  191. data/lib/active_record/test_databases.rb +14 -4
  192. data/lib/active_record/test_fixtures.rb +123 -89
  193. data/lib/active_record/testing/query_assertions.rb +127 -0
  194. data/lib/active_record/timestamp.rb +6 -4
  195. data/lib/active_record/token_for.rb +22 -12
  196. data/lib/active_record/touch_later.rb +1 -1
  197. data/lib/active_record/transaction.rb +129 -0
  198. data/lib/active_record/transactions.rb +97 -18
  199. data/lib/active_record/translation.rb +0 -2
  200. data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
  201. data/lib/active_record/type/internal/timezone.rb +7 -0
  202. data/lib/active_record/type/json.rb +15 -2
  203. data/lib/active_record/type/serialized.rb +12 -7
  204. data/lib/active_record/type/type_map.rb +1 -1
  205. data/lib/active_record/type_caster/connection.rb +5 -4
  206. data/lib/active_record/validations/associated.rb +10 -4
  207. data/lib/active_record/validations/uniqueness.rb +23 -18
  208. data/lib/active_record/validations.rb +4 -1
  209. data/lib/active_record.rb +203 -59
  210. data/lib/arel/alias_predication.rb +3 -1
  211. data/lib/arel/collectors/bind.rb +4 -2
  212. data/lib/arel/collectors/composite.rb +7 -0
  213. data/lib/arel/collectors/sql_string.rb +2 -2
  214. data/lib/arel/collectors/substitute_binds.rb +3 -3
  215. data/lib/arel/crud.rb +8 -11
  216. data/lib/arel/delete_manager.rb +5 -0
  217. data/lib/arel/nodes/binary.rb +1 -7
  218. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  219. data/lib/arel/nodes/count.rb +2 -2
  220. data/lib/arel/nodes/delete_statement.rb +4 -2
  221. data/lib/arel/nodes/function.rb +4 -10
  222. data/lib/arel/nodes/named_function.rb +2 -2
  223. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  224. data/lib/arel/nodes/node.rb +6 -5
  225. data/lib/arel/nodes/sql_literal.rb +8 -1
  226. data/lib/arel/nodes/update_statement.rb +4 -2
  227. data/lib/arel/nodes.rb +2 -4
  228. data/lib/arel/predications.rb +1 -1
  229. data/lib/arel/select_manager.rb +14 -5
  230. data/lib/arel/table.rb +3 -7
  231. data/lib/arel/tree_manager.rb +3 -2
  232. data/lib/arel/update_manager.rb +7 -1
  233. data/lib/arel/visitors/dot.rb +3 -3
  234. data/lib/arel/visitors/mysql.rb +9 -4
  235. data/lib/arel/visitors/postgresql.rb +56 -12
  236. data/lib/arel/visitors/sqlite.rb +80 -8
  237. data/lib/arel/visitors/to_sql.rb +34 -37
  238. data/lib/arel.rb +10 -4
  239. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  240. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  241. metadata +23 -14
  242. data/lib/active_record/explain_subscriber.rb +0 -34
  243. data/lib/active_record/normalization.rb +0 -167
  244. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -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,20 +115,19 @@ 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 = {}
114
125
  opclasses = {}
115
- include_columns = include ? include.split(",").map(&:strip) : []
126
+ include_columns = include ? include.split(",").map { |c| Utils.unquote_identifier(c.strip.gsub('""', '"')) } : []
116
127
 
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")
@@ -209,8 +270,16 @@ module ActiveRecord
209
270
  end
210
271
 
211
272
  # Creates a schema for the given schema name.
212
- def create_schema(schema_name)
213
- execute "CREATE SCHEMA #{quote_schema_name(schema_name)}"
273
+ def create_schema(schema_name, force: nil, if_not_exists: nil)
274
+ if force && if_not_exists
275
+ raise ArgumentError, "Options `:force` and `:if_not_exists` cannot be used simultaneously."
276
+ end
277
+
278
+ if force
279
+ drop_schema(schema_name, if_exists: true)
280
+ end
281
+
282
+ execute("CREATE SCHEMA#{' IF NOT EXISTS' if if_not_exists} #{quote_schema_name(schema_name)}")
214
283
  end
215
284
 
216
285
  # Drops the schema for the given schema name.
@@ -218,12 +287,18 @@ module ActiveRecord
218
287
  execute "DROP SCHEMA#{' IF EXISTS' if options[:if_exists]} #{quote_schema_name(schema_name)} CASCADE"
219
288
  end
220
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
+
221
295
  # Sets the schema search path to a string of comma-separated schema names.
222
296
  # Names beginning with $ have to be quoted (e.g. $user => '$user').
223
297
  # See: https://www.postgresql.org/docs/current/static/ddl-schemas.html
224
298
  #
225
299
  # This should be not be called manually but set in database.yml.
226
300
  def schema_search_path=(schema_csv)
301
+ return if schema_csv == @schema_search_path
227
302
  if schema_csv
228
303
  internal_execute("SET search_path TO #{schema_csv}")
229
304
  @schema_search_path = schema_csv
@@ -242,7 +317,7 @@ module ActiveRecord
242
317
 
243
318
  # Set the client message level.
244
319
  def client_min_messages=(level)
245
- internal_execute("SET client_min_messages TO '#{level}'")
320
+ internal_execute("SET client_min_messages TO '#{level}'", "SCHEMA")
246
321
  end
247
322
 
248
323
  # Returns the sequence name for a table's primary key or some other specified key.
@@ -268,7 +343,7 @@ module ActiveRecord
268
343
  if sequence
269
344
  quoted_sequence = quote_table_name(sequence)
270
345
 
271
- query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
346
+ internal_execute("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA")
272
347
  else
273
348
  @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
274
349
  end
@@ -299,7 +374,7 @@ module ActiveRecord
299
374
  end
300
375
  end
301
376
 
302
- 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")
303
378
  end
304
379
  end
305
380
 
@@ -360,16 +435,13 @@ module ActiveRecord
360
435
  def primary_keys(table_name) # :nodoc:
361
436
  query_values(<<~SQL, "SCHEMA")
362
437
  SELECT a.attname
363
- FROM (
364
- SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx
365
- FROM pg_index
366
- WHERE indrelid = #{quote(quote_table_name(table_name))}::regclass
367
- AND indisprimary
368
- ) i
369
- JOIN pg_attribute a
370
- ON a.attrelid = i.indrelid
371
- AND a.attnum = i.indkey[i.idx]
372
- 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)
373
445
  SQL
374
446
  end
375
447
 
@@ -524,14 +596,6 @@ module ActiveRecord
524
596
  end
525
597
 
526
598
  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
599
  assert_valid_deferrable(options[:deferrable])
536
600
 
537
601
  super
@@ -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
 
@@ -692,6 +775,10 @@ module ActiveRecord
692
775
  # The constraint name. Defaults to <tt>excl_rails_<identifier></tt>.
693
776
  # [<tt>:deferrable</tt>]
694
777
  # 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+.
778
+ # [<tt>:using</tt>]
779
+ # Specify which index method to use when creating this exclusion constraint (e.g. +:btree+, +:gist+ etc).
780
+ # [<tt>:where</tt>]
781
+ # Specify an exclusion constraint on a subset of the table (internally PostgreSQL creates a partial index for this).
695
782
  def add_exclusion_constraint(table_name, expression, **options)
696
783
  options = exclusion_constraint_options(table_name, expression, options)
697
784
  at = create_alter_table(table_name)
@@ -718,15 +805,12 @@ module ActiveRecord
718
805
  def remove_exclusion_constraint(table_name, expression = nil, **options)
719
806
  excl_name_to_delete = exclusion_constraint_for!(table_name, expression: expression, **options).name
720
807
 
721
- at = create_alter_table(table_name)
722
- at.drop_exclusion_constraint(excl_name_to_delete)
723
-
724
- execute schema_creation.accept(at)
808
+ remove_constraint(table_name, excl_name_to_delete)
725
809
  end
726
810
 
727
811
  # Adds a new unique constraint to the table.
728
812
  #
729
- # 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
730
814
  #
731
815
  # generates:
732
816
  #
@@ -743,6 +827,9 @@ module ActiveRecord
743
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+.
744
828
  # [<tt>:using_index</tt>]
745
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.
746
833
  def add_unique_constraint(table_name, column_name = nil, **options)
747
834
  options = unique_constraint_options(table_name, column_name, options)
748
835
  at = create_alter_table(table_name)
@@ -773,10 +860,7 @@ module ActiveRecord
773
860
  def remove_unique_constraint(table_name, column_name = nil, **options)
774
861
  unique_name_to_delete = unique_constraint_for!(table_name, column: column_name, **options).name
775
862
 
776
- at = create_alter_table(table_name)
777
- at.drop_unique_constraint(unique_name_to_delete)
778
-
779
- execute schema_creation.accept(at)
863
+ remove_constraint(table_name, unique_name_to_delete)
780
864
  end
781
865
 
782
866
  # Maps logical Rails types to PostgreSQL-specific data types.
@@ -875,7 +959,7 @@ module ActiveRecord
875
959
  #
876
960
  # validate_check_constraint :products, name: "price_check"
877
961
  #
878
- # 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].
879
963
  def validate_check_constraint(table_name, **options)
880
964
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
881
965
 
@@ -933,6 +1017,7 @@ module ActiveRecord
933
1017
 
934
1018
  PostgreSQL::Column.new(
935
1019
  column_name,
1020
+ get_oid_type(oid.to_i, fmod.to_i, column_name, type),
936
1021
  default_value,
937
1022
  type_metadata,
938
1023
  !notnull,
@@ -1102,13 +1187,8 @@ module ActiveRecord
1102
1187
  [name.schema, name.identifier]
1103
1188
  end
1104
1189
 
1105
- def column_names_from_column_numbers(table_oid, column_numbers)
1106
- Hash[query(<<~SQL, "SCHEMA")].values_at(*column_numbers).compact
1107
- SELECT a.attnum, a.attname
1108
- FROM pg_attribute a
1109
- WHERE a.attrelid = #{table_oid}
1110
- AND a.attnum IN (#{column_numbers.join(", ")})
1111
- SQL
1190
+ def decode_string_array(value)
1191
+ PG::TextDecoder::Array.new.decode(value)
1112
1192
  end
1113
1193
  end
1114
1194
  end