activerecord 7.0.8.7 → 7.1.0.beta1

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 (227) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1339 -1572
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +15 -16
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -9
  15. data/lib/active_record/associations/collection_proxy.rb +16 -11
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +20 -13
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +10 -8
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader.rb +12 -9
  23. data/lib/active_record/associations/singular_association.rb +1 -1
  24. data/lib/active_record/associations/through_association.rb +22 -11
  25. data/lib/active_record/associations.rb +193 -97
  26. data/lib/active_record/attribute_assignment.rb +0 -2
  27. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  28. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  29. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  30. data/lib/active_record/attribute_methods/query.rb +28 -16
  31. data/lib/active_record/attribute_methods/read.rb +18 -5
  32. data/lib/active_record/attribute_methods/serialization.rb +150 -31
  33. data/lib/active_record/attribute_methods/write.rb +3 -3
  34. data/lib/active_record/attribute_methods.rb +105 -21
  35. data/lib/active_record/attributes.rb +3 -3
  36. data/lib/active_record/autosave_association.rb +55 -9
  37. data/lib/active_record/base.rb +7 -2
  38. data/lib/active_record/callbacks.rb +10 -24
  39. data/lib/active_record/coders/column_serializer.rb +61 -0
  40. data/lib/active_record/coders/json.rb +1 -1
  41. data/lib/active_record/coders/yaml_column.rb +70 -42
  42. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +163 -88
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  45. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  46. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  47. data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
  48. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  49. data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
  50. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  51. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  52. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  53. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +289 -122
  54. data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
  55. data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
  57. data/lib/active_record/connection_adapters/column.rb +9 -0
  58. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  59. data/lib/active_record/connection_adapters/mysql/database_statements.rb +22 -143
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
  61. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  62. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  63. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  64. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -12
  65. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  66. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  67. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  68. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  69. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  70. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
  71. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  72. data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
  73. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  74. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  75. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  76. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
  77. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
  78. data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
  79. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  80. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  81. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
  82. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
  86. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  87. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  88. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  89. data/lib/active_record/connection_adapters.rb +3 -1
  90. data/lib/active_record/connection_handling.rb +71 -94
  91. data/lib/active_record/core.rb +128 -138
  92. data/lib/active_record/counter_cache.rb +46 -25
  93. data/lib/active_record/database_configurations/database_config.rb +9 -3
  94. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  95. data/lib/active_record/database_configurations/url_config.rb +17 -11
  96. data/lib/active_record/database_configurations.rb +86 -33
  97. data/lib/active_record/delegated_type.rb +8 -3
  98. data/lib/active_record/deprecator.rb +7 -0
  99. data/lib/active_record/destroy_association_async_job.rb +2 -0
  100. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  101. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  102. data/lib/active_record/encryption/config.rb +25 -1
  103. data/lib/active_record/encryption/configurable.rb +12 -19
  104. data/lib/active_record/encryption/context.rb +10 -3
  105. data/lib/active_record/encryption/contexts.rb +5 -1
  106. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  107. data/lib/active_record/encryption/encryptable_record.rb +36 -18
  108. data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
  109. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
  110. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
  111. data/lib/active_record/encryption/key_generator.rb +12 -1
  112. data/lib/active_record/encryption/message_serializer.rb +2 -0
  113. data/lib/active_record/encryption/properties.rb +3 -3
  114. data/lib/active_record/encryption/scheme.rb +19 -22
  115. data/lib/active_record/encryption.rb +1 -0
  116. data/lib/active_record/enum.rb +113 -26
  117. data/lib/active_record/errors.rb +89 -15
  118. data/lib/active_record/explain.rb +23 -3
  119. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  120. data/lib/active_record/fixture_set/render_context.rb +2 -0
  121. data/lib/active_record/fixture_set/table_row.rb +29 -8
  122. data/lib/active_record/fixtures.rb +119 -71
  123. data/lib/active_record/future_result.rb +30 -5
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +30 -16
  126. data/lib/active_record/insert_all.rb +55 -8
  127. data/lib/active_record/integration.rb +8 -8
  128. data/lib/active_record/internal_metadata.rb +118 -30
  129. data/lib/active_record/locking/pessimistic.rb +5 -2
  130. data/lib/active_record/log_subscriber.rb +29 -12
  131. data/lib/active_record/marshalling.rb +56 -0
  132. data/lib/active_record/message_pack.rb +124 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  134. data/lib/active_record/middleware/database_selector.rb +5 -7
  135. data/lib/active_record/middleware/shard_selector.rb +3 -1
  136. data/lib/active_record/migration/command_recorder.rb +100 -4
  137. data/lib/active_record/migration/compatibility.rb +131 -5
  138. data/lib/active_record/migration/default_strategy.rb +23 -0
  139. data/lib/active_record/migration/execution_strategy.rb +19 -0
  140. data/lib/active_record/migration.rb +213 -109
  141. data/lib/active_record/model_schema.rb +47 -27
  142. data/lib/active_record/nested_attributes.rb +28 -3
  143. data/lib/active_record/normalization.rb +158 -0
  144. data/lib/active_record/persistence.rb +183 -33
  145. data/lib/active_record/promise.rb +84 -0
  146. data/lib/active_record/query_cache.rb +3 -21
  147. data/lib/active_record/query_logs.rb +77 -52
  148. data/lib/active_record/query_logs_formatter.rb +41 -0
  149. data/lib/active_record/querying.rb +15 -2
  150. data/lib/active_record/railtie.rb +107 -45
  151. data/lib/active_record/railties/controller_runtime.rb +10 -5
  152. data/lib/active_record/railties/databases.rake +139 -145
  153. data/lib/active_record/railties/job_runtime.rb +23 -0
  154. data/lib/active_record/readonly_attributes.rb +32 -5
  155. data/lib/active_record/reflection.rb +169 -45
  156. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  157. data/lib/active_record/relation/batches.rb +190 -61
  158. data/lib/active_record/relation/calculations.rb +152 -63
  159. data/lib/active_record/relation/delegation.rb +22 -8
  160. data/lib/active_record/relation/finder_methods.rb +85 -15
  161. data/lib/active_record/relation/merger.rb +2 -0
  162. data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
  163. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  164. data/lib/active_record/relation/predicate_builder.rb +26 -14
  165. data/lib/active_record/relation/query_attribute.rb +2 -1
  166. data/lib/active_record/relation/query_methods.rb +351 -62
  167. data/lib/active_record/relation/spawn_methods.rb +18 -1
  168. data/lib/active_record/relation.rb +76 -35
  169. data/lib/active_record/result.rb +19 -5
  170. data/lib/active_record/runtime_registry.rb +10 -1
  171. data/lib/active_record/sanitization.rb +51 -11
  172. data/lib/active_record/schema.rb +2 -3
  173. data/lib/active_record/schema_dumper.rb +41 -7
  174. data/lib/active_record/schema_migration.rb +68 -33
  175. data/lib/active_record/scoping/default.rb +15 -5
  176. data/lib/active_record/scoping/named.rb +2 -2
  177. data/lib/active_record/scoping.rb +2 -1
  178. data/lib/active_record/secure_password.rb +60 -0
  179. data/lib/active_record/secure_token.rb +21 -3
  180. data/lib/active_record/signed_id.rb +7 -5
  181. data/lib/active_record/store.rb +8 -8
  182. data/lib/active_record/suppressor.rb +3 -1
  183. data/lib/active_record/table_metadata.rb +10 -1
  184. data/lib/active_record/tasks/database_tasks.rb +127 -105
  185. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  186. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  187. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -7
  188. data/lib/active_record/test_fixtures.rb +113 -96
  189. data/lib/active_record/timestamp.rb +26 -14
  190. data/lib/active_record/token_for.rb +113 -0
  191. data/lib/active_record/touch_later.rb +11 -6
  192. data/lib/active_record/transactions.rb +36 -10
  193. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  194. data/lib/active_record/type/internal/timezone.rb +7 -2
  195. data/lib/active_record/type/time.rb +4 -0
  196. data/lib/active_record/validations/absence.rb +1 -1
  197. data/lib/active_record/validations/numericality.rb +5 -4
  198. data/lib/active_record/validations/presence.rb +5 -28
  199. data/lib/active_record/validations/uniqueness.rb +47 -2
  200. data/lib/active_record/validations.rb +8 -4
  201. data/lib/active_record/version.rb +1 -1
  202. data/lib/active_record.rb +121 -16
  203. data/lib/arel/errors.rb +10 -0
  204. data/lib/arel/factory_methods.rb +4 -0
  205. data/lib/arel/nodes/binary.rb +6 -1
  206. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  207. data/lib/arel/nodes/cte.rb +36 -0
  208. data/lib/arel/nodes/fragments.rb +35 -0
  209. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  210. data/lib/arel/nodes/leading_join.rb +8 -0
  211. data/lib/arel/nodes/node.rb +111 -2
  212. data/lib/arel/nodes/sql_literal.rb +6 -0
  213. data/lib/arel/nodes/table_alias.rb +4 -0
  214. data/lib/arel/nodes.rb +4 -0
  215. data/lib/arel/predications.rb +2 -0
  216. data/lib/arel/table.rb +9 -5
  217. data/lib/arel/visitors/mysql.rb +8 -1
  218. data/lib/arel/visitors/to_sql.rb +81 -17
  219. data/lib/arel/visitors/visitor.rb +2 -2
  220. data/lib/arel.rb +16 -2
  221. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  222. data/lib/rails/generators/active_record/migration.rb +3 -1
  223. data/lib/rails/generators/active_record/model/USAGE +113 -0
  224. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  225. metadata +52 -17
  226. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  227. data/lib/active_record/null_relation.rb +0 -63
@@ -21,28 +21,19 @@ require "active_record/connection_adapters/postgresql/utils"
21
21
 
22
22
  module ActiveRecord
23
23
  module ConnectionHandling # :nodoc:
24
+ def postgresql_adapter_class
25
+ ConnectionAdapters::PostgreSQLAdapter
26
+ end
27
+
24
28
  # Establishes a connection to the database that's used by all Active Record objects
25
29
  def postgresql_connection(config)
26
- conn_params = config.symbolize_keys.compact
27
-
28
- # Map ActiveRecords param names to PGs.
29
- conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
30
- conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
31
-
32
- # Forward only valid config params to PG::Connection.connect.
33
- valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
34
- conn_params.slice!(*valid_conn_param_keys)
35
-
36
- ConnectionAdapters::PostgreSQLAdapter.new(
37
- ConnectionAdapters::PostgreSQLAdapter.new_client(conn_params),
38
- logger,
39
- conn_params,
40
- config,
41
- )
30
+ postgresql_adapter_class.new(config)
42
31
  end
43
32
  end
44
33
 
45
34
  module ConnectionAdapters
35
+ # = Active Record PostgreSQL Adapter
36
+ #
46
37
  # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
47
38
  #
48
39
  # Options:
@@ -77,16 +68,37 @@ module ActiveRecord
77
68
  def new_client(conn_params)
78
69
  PG.connect(**conn_params)
79
70
  rescue ::PG::Error => error
80
- if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
71
+ if conn_params && conn_params[:dbname] == "postgres"
72
+ raise ActiveRecord::ConnectionNotEstablished, error.message
73
+ elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
81
74
  raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
82
75
  elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
83
76
  raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
84
- elsif conn_params && conn_params[:hostname] && error.message.include?(conn_params[:hostname])
85
- raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:hostname])
77
+ elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
78
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
86
79
  else
87
80
  raise ActiveRecord::ConnectionNotEstablished, error.message
88
81
  end
89
82
  end
83
+
84
+ def dbconsole(config, options = {})
85
+ pg_config = config.configuration_hash
86
+
87
+ ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
88
+ ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
89
+ ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
90
+ ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
91
+ ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
92
+ ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
93
+ ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
94
+ ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
95
+ if pg_config[:variables]
96
+ ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
97
+ "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
98
+ end.join(" ")
99
+ end
100
+ find_cmd_and_exec("psql", config.database)
101
+ end
90
102
  end
91
103
 
92
104
  ##
@@ -105,7 +117,7 @@ module ActiveRecord
105
117
  ##
106
118
  # :singleton-method:
107
119
  # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
108
- # in migrations, Rails will translate this to a PostgreSQL "timestamp without time zone".
120
+ # in migrations, \Rails will translate this to a PostgreSQL "timestamp without time zone".
109
121
  # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
110
122
  # store DateTimes as "timestamp with time zone":
111
123
  #
@@ -183,13 +195,17 @@ module ActiveRecord
183
195
  end
184
196
 
185
197
  def supports_partitioned_indexes?
186
- database_version >= 110_000 # >= 11.0
198
+ database_version >= 11_00_00 # >= 11.0
187
199
  end
188
200
 
189
201
  def supports_partial_index?
190
202
  true
191
203
  end
192
204
 
205
+ def supports_index_include?
206
+ database_version >= 11_00_00 # >= 11.0
207
+ end
208
+
193
209
  def supports_expression_index?
194
210
  true
195
211
  end
@@ -206,6 +222,14 @@ module ActiveRecord
206
222
  true
207
223
  end
208
224
 
225
+ def supports_exclusion_constraints?
226
+ true
227
+ end
228
+
229
+ def supports_unique_keys?
230
+ true
231
+ end
232
+
209
233
  def supports_validate_constraints?
210
234
  true
211
235
  end
@@ -234,25 +258,37 @@ module ActiveRecord
234
258
  true
235
259
  end
236
260
 
261
+ def supports_restart_db_transaction?
262
+ database_version >= 12_00_00 # >= 12.0
263
+ end
264
+
237
265
  def supports_insert_returning?
238
266
  true
239
267
  end
240
268
 
241
269
  def supports_insert_on_conflict?
242
- database_version >= 90500 # >= 9.5
270
+ database_version >= 9_05_00 # >= 9.5
243
271
  end
244
272
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
245
273
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
246
274
  alias supports_insert_conflict_target? supports_insert_on_conflict?
247
275
 
248
276
  def supports_virtual_columns?
249
- database_version >= 120_000 # >= 12.0
277
+ database_version >= 12_00_00 # >= 12.0
278
+ end
279
+
280
+ def supports_nulls_not_distinct?
281
+ database_version >= 15_00_00 # >= 15.0
250
282
  end
251
283
 
252
284
  def index_algorithms
253
285
  { concurrently: "CONCURRENTLY" }
254
286
  end
255
287
 
288
+ def return_value_after_insert?(column) # :nodoc:
289
+ column.auto_populated?
290
+ end
291
+
256
292
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
257
293
  def initialize(connection, max)
258
294
  super(max)
@@ -266,47 +302,46 @@ module ActiveRecord
266
302
 
267
303
  private
268
304
  def dealloc(key)
269
- @connection.query "DEALLOCATE #{key}" if connection_active?
270
- rescue PG::Error
271
- end
272
-
273
- def connection_active?
274
- @connection.status == PG::CONNECTION_OK
305
+ # This is ugly, but safe: the statement pool is only
306
+ # accessed while holding the connection's lock. (And we
307
+ # don't need the complication of with_raw_connection because
308
+ # a reconnect would invalidate the entire statement pool.)
309
+ if conn = @connection.instance_variable_get(:@raw_connection)
310
+ conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
311
+ end
275
312
  rescue PG::Error
276
- false
277
313
  end
278
314
  end
279
315
 
280
316
  # Initializes and connects a PostgreSQL adapter.
281
- def initialize(connection, logger, connection_parameters, config)
282
- super(connection, logger, config)
317
+ def initialize(...)
318
+ super
283
319
 
284
- @connection_parameters = connection_parameters || {}
320
+ conn_params = @config.compact
285
321
 
286
- # @local_tz is initialized as nil to avoid warnings when connect tries to use it
287
- @local_tz = nil
288
- @max_identifier_length = nil
322
+ # Map ActiveRecords param names to PGs.
323
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
324
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
289
325
 
290
- configure_connection
291
- add_pg_encoders
292
- add_pg_decoders
326
+ # Forward only valid config params to PG::Connection.connect.
327
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
328
+ conn_params.slice!(*valid_conn_param_keys)
293
329
 
294
- @type_map = Type::HashLookupTypeMap.new
295
- initialize_type_map
296
- @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
297
- @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
298
- end
330
+ @connection_parameters = conn_params
299
331
 
300
- def self.database_exists?(config)
301
- !!ActiveRecord::Base.postgresql_connection(config)
302
- rescue ActiveRecord::NoDatabaseError
303
- false
332
+ @max_identifier_length = nil
333
+ @type_map = nil
334
+ @raw_connection = nil
335
+ @notice_receiver_sql_warnings = []
336
+
337
+ @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
304
338
  end
305
339
 
306
340
  # Is this connection alive and ready for queries?
307
341
  def active?
308
342
  @lock.synchronize do
309
- @connection.query ";"
343
+ return false unless @raw_connection
344
+ @raw_connection.query ";"
310
345
  end
311
346
  true
312
347
  rescue PG::Error
@@ -314,31 +349,27 @@ module ActiveRecord
314
349
  end
315
350
 
316
351
  def reload_type_map # :nodoc:
317
- type_map.clear
318
- initialize_type_map
319
- end
320
-
321
- # Close then reopen the connection.
322
- def reconnect!
323
352
  @lock.synchronize do
324
- super
325
- @connection.reset
326
- configure_connection
327
- reload_type_map
328
- rescue PG::ConnectionBad
329
- connect
353
+ if @type_map
354
+ type_map.clear
355
+ else
356
+ @type_map = Type::HashLookupTypeMap.new
357
+ end
358
+
359
+ initialize_type_map
330
360
  end
331
361
  end
332
362
 
333
363
  def reset!
334
364
  @lock.synchronize do
335
- clear_cache!
336
- reset_transaction
337
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
338
- @connection.query "ROLLBACK"
365
+ return connect! unless @raw_connection
366
+
367
+ unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
368
+ @raw_connection.query "ROLLBACK"
339
369
  end
340
- @connection.query "DISCARD ALL"
341
- configure_connection
370
+ @raw_connection.query "DISCARD ALL"
371
+
372
+ super
342
373
  end
343
374
  end
344
375
 
@@ -347,14 +378,15 @@ module ActiveRecord
347
378
  def disconnect!
348
379
  @lock.synchronize do
349
380
  super
350
- @connection.close rescue nil
381
+ @raw_connection&.close rescue nil
382
+ @raw_connection = nil
351
383
  end
352
384
  end
353
385
 
354
386
  def discard! # :nodoc:
355
387
  super
356
- @connection.socket_io.reopen(IO::NULL) rescue nil
357
- @connection = nil
388
+ @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
389
+ @raw_connection = nil
358
390
  end
359
391
 
360
392
  def native_database_types # :nodoc:
@@ -370,7 +402,7 @@ module ActiveRecord
370
402
  end
371
403
 
372
404
  def set_standard_conforming_strings
373
- execute("SET standard_conforming_strings = on", "SCHEMA")
405
+ internal_execute("SET standard_conforming_strings = on")
374
406
  end
375
407
 
376
408
  def supports_ddl_transactions?
@@ -398,7 +430,7 @@ module ActiveRecord
398
430
  end
399
431
 
400
432
  def supports_pgcrypto_uuid?
401
- database_version >= 90400 # >= 9.4
433
+ database_version >= 9_04_00 # >= 9.4
402
434
  end
403
435
 
404
436
  def supports_optimizer_hints?
@@ -430,14 +462,21 @@ module ActiveRecord
430
462
  query_value("SELECT pg_advisory_unlock(#{lock_id})")
431
463
  end
432
464
 
433
- def enable_extension(name)
434
- exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
435
- reload_type_map
436
- }
465
+ def enable_extension(name, **)
466
+ schema, name = name.to_s.split(".").values_at(-2, -1)
467
+ sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
468
+ sql << " SCHEMA #{schema}" if schema
469
+
470
+ internal_exec_query(sql).tap { reload_type_map }
437
471
  end
438
472
 
439
- def disable_extension(name)
440
- exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
473
+ # Removes an extension from the database.
474
+ #
475
+ # [<tt>:force</tt>]
476
+ # Set to +:cascade+ to drop dependent objects as well.
477
+ # Defaults to false.
478
+ def disable_extension(name, force: false)
479
+ internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
441
480
  reload_type_map
442
481
  }
443
482
  end
@@ -451,7 +490,7 @@ module ActiveRecord
451
490
  end
452
491
 
453
492
  def extensions
454
- exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
493
+ internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
455
494
  end
456
495
 
457
496
  # Returns a list of defined enum types, and their values.
@@ -459,31 +498,97 @@ module ActiveRecord
459
498
  query = <<~SQL
460
499
  SELECT
461
500
  type.typname AS name,
501
+ type.OID AS oid,
502
+ n.nspname AS schema,
462
503
  string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
463
504
  FROM pg_enum AS enum
464
- JOIN pg_type AS type
465
- ON (type.oid = enum.enumtypid)
466
- GROUP BY type.typname;
505
+ JOIN pg_type AS type ON (type.oid = enum.enumtypid)
506
+ JOIN pg_namespace n ON type.typnamespace = n.oid
507
+ WHERE n.nspname = ANY (current_schemas(false))
508
+ GROUP BY type.OID, n.nspname, type.typname;
467
509
  SQL
468
- exec_query(query, "SCHEMA").cast_values
510
+
511
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
512
+ name, schema = row[0], row[2]
513
+ schema = nil if schema == current_schema
514
+ full_name = [schema, name].compact.join(".")
515
+ memo[full_name] = row.last
516
+ end.to_a
469
517
  end
470
518
 
471
519
  # Given a name and an array of values, creates an enum type.
472
- def create_enum(name, values)
473
- sql_values = values.map { |s| "'#{s}'" }.join(", ")
520
+ def create_enum(name, values, **options)
521
+ sql_values = values.map { |s| quote(s) }.join(", ")
522
+ scope = quoted_scope(name)
474
523
  query = <<~SQL
475
524
  DO $$
476
525
  BEGIN
477
526
  IF NOT EXISTS (
478
- SELECT 1 FROM pg_type t
479
- WHERE t.typname = '#{name}'
527
+ SELECT 1
528
+ FROM pg_type t
529
+ JOIN pg_namespace n ON t.typnamespace = n.oid
530
+ WHERE t.typname = #{scope[:name]}
531
+ AND n.nspname = #{scope[:schema]}
480
532
  ) THEN
481
- CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
533
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
482
534
  END IF;
483
535
  END
484
536
  $$;
485
537
  SQL
486
- exec_query(query)
538
+ internal_exec_query(query)
539
+ end
540
+
541
+ # Drops an enum type.
542
+ #
543
+ # If the <tt>if_exists: true</tt> option is provided, the enum is dropped
544
+ # only if it exists. Otherwise, if the enum doesn't exist, an error is
545
+ # raised.
546
+ #
547
+ # The +values+ parameter will be ignored if present. It can be helpful
548
+ # to provide this in a migration's +change+ method so it can be reverted.
549
+ # In that case, +values+ will be used by #create_enum.
550
+ def drop_enum(name, values = nil, **options)
551
+ query = <<~SQL
552
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
553
+ SQL
554
+ internal_exec_query(query)
555
+ end
556
+
557
+ # Rename an existing enum type to something else.
558
+ def rename_enum(name, options = {})
559
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
560
+
561
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
562
+ end
563
+
564
+ # Add enum value to an existing enum type.
565
+ def add_enum_value(type_name, value, options = {})
566
+ before, after = options.values_at(:before, :after)
567
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
568
+
569
+ if before && after
570
+ raise ArgumentError, "Cannot have both :before and :after at the same time"
571
+ elsif before
572
+ sql << " BEFORE '#{before}'"
573
+ elsif after
574
+ sql << " AFTER '#{after}'"
575
+ end
576
+
577
+ execute(sql).tap { reload_type_map }
578
+ end
579
+
580
+ # Rename enum value on an existing enum type.
581
+ def rename_enum_value(type_name, options = {})
582
+ unless database_version >= 10_00_00 # >= 10.0
583
+ raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
584
+ end
585
+
586
+ from = options.fetch(:from) { raise ArgumentError, ":from is required" }
587
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
588
+
589
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
590
+ reload_type_map
591
+ }
487
592
  end
488
593
 
489
594
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -491,10 +596,18 @@ module ActiveRecord
491
596
  @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i
492
597
  end
493
598
 
599
+ # Returns the maximum length of a table name.
600
+ def table_name_length
601
+ # PostgreSQL automatically creates an index for PRIMARY KEY with name consisting of
602
+ # truncated table name and "_pkey" suffix fitting into max_identifier_length number of characters.
603
+ # We allow smaller table names to be able to correctly rename this index when renaming the table.
604
+ max_identifier_length - "_pkey".length
605
+ end
606
+
494
607
  # Set the authorized user for this session
495
608
  def session_auth=(user)
496
609
  clear_cache!
497
- execute("SET SESSION AUTHORIZATION #{user}")
610
+ internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
498
611
  end
499
612
 
500
613
  def use_insert_returning?
@@ -503,7 +616,7 @@ module ActiveRecord
503
616
 
504
617
  # Returns the version of the connected PostgreSQL server.
505
618
  def get_database_version # :nodoc:
506
- @connection.server_version
619
+ valid_raw_connection.server_version
507
620
  end
508
621
  alias :postgresql_version :database_version
509
622
 
@@ -531,7 +644,7 @@ module ActiveRecord
531
644
  end
532
645
 
533
646
  def check_version # :nodoc:
534
- if database_version < 90300 # < 9.3
647
+ if database_version < 9_03_00 # < 9.3
535
648
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
536
649
  end
537
650
  end
@@ -575,10 +688,6 @@ module ActiveRecord
575
688
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
576
689
  m.register_type "circle", OID::SpecializedString.new(:circle)
577
690
 
578
- register_class_with_precision m, "time", Type::Time
579
- register_class_with_precision m, "timestamp", OID::Timestamp
580
- register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
581
-
582
691
  m.register_type "numeric" do |_, fmod, sql_type|
583
692
  precision = extract_precision(sql_type)
584
693
  scale = extract_scale(sql_type)
@@ -613,6 +722,11 @@ module ActiveRecord
613
722
 
614
723
  def initialize_type_map(m = type_map)
615
724
  self.class.initialize_type_map(m)
725
+
726
+ self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone
727
+ self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone
728
+ self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
729
+
616
730
  load_additional_types
617
731
  end
618
732
 
@@ -669,35 +783,53 @@ module ActiveRecord
669
783
  case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
670
784
  when nil
671
785
  if exception.message.match?(/connection is closed/i)
672
- ConnectionNotEstablished.new(exception)
786
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
787
+ elsif exception.is_a?(PG::ConnectionBad)
788
+ # libpq message style always ends with a newline; the pg gem's internal
789
+ # errors do not. We separate these cases because a pg-internal
790
+ # ConnectionBad means it failed before it managed to send the query,
791
+ # whereas a libpq failure could have occurred at any time (meaning the
792
+ # server may have already executed part or all of the query).
793
+ if exception.message.end_with?("\n")
794
+ ConnectionFailed.new(exception, connection_pool: @pool)
795
+ else
796
+ ConnectionNotEstablished.new(exception, connection_pool: @pool)
797
+ end
673
798
  else
674
799
  super
675
800
  end
676
801
  when UNIQUE_VIOLATION
677
- RecordNotUnique.new(message, sql: sql, binds: binds)
802
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
678
803
  when FOREIGN_KEY_VIOLATION
679
- InvalidForeignKey.new(message, sql: sql, binds: binds)
804
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
680
805
  when VALUE_LIMIT_VIOLATION
681
- ValueTooLong.new(message, sql: sql, binds: binds)
806
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
682
807
  when NUMERIC_VALUE_OUT_OF_RANGE
683
- RangeError.new(message, sql: sql, binds: binds)
808
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
684
809
  when NOT_NULL_VIOLATION
685
- NotNullViolation.new(message, sql: sql, binds: binds)
810
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
686
811
  when SERIALIZATION_FAILURE
687
- SerializationFailure.new(message, sql: sql, binds: binds)
812
+ SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
688
813
  when DEADLOCK_DETECTED
689
- Deadlocked.new(message, sql: sql, binds: binds)
814
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
690
815
  when DUPLICATE_DATABASE
691
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
816
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
692
817
  when LOCK_NOT_AVAILABLE
693
- LockWaitTimeout.new(message, sql: sql, binds: binds)
818
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
694
819
  when QUERY_CANCELED
695
- QueryCanceled.new(message, sql: sql, binds: binds)
820
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
696
821
  else
697
822
  super
698
823
  end
699
824
  end
700
825
 
826
+ def retryable_query_error?(exception)
827
+ # We cannot retry anything if we're inside a broken transaction; we need to at
828
+ # least raise until the innermost savepoint is rolled back
829
+ @raw_connection&.transaction_status != ::PG::PQTRANS_INERROR &&
830
+ super
831
+ end
832
+
701
833
  def get_oid_type(oid, fmod, column_name, sql_type = "")
702
834
  if !type_map.key?(oid)
703
835
  load_additional_types([oid])
@@ -714,7 +846,7 @@ module ActiveRecord
714
846
  def load_additional_types(oids = nil)
715
847
  initializer = OID::TypeMapInitializer.new(type_map)
716
848
  load_types_queries(initializer, oids) do |query|
717
- execute_and_clear(query, "SCHEMA", []) do |records|
849
+ execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
718
850
  initializer.run(records)
719
851
  end
720
852
  end
@@ -737,14 +869,14 @@ module ActiveRecord
737
869
 
738
870
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
739
871
 
740
- def execute_and_clear(sql, name, binds, prepare: false, async: false)
872
+ def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
741
873
  sql = transform_query(sql)
742
874
  check_if_write_query(sql)
743
875
 
744
876
  if !prepare || without_prepared_statement?(binds)
745
- result = exec_no_cache(sql, name, binds, async: async)
877
+ result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
746
878
  else
747
- result = exec_cache(sql, name, binds, async: async)
879
+ result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
748
880
  end
749
881
  begin
750
882
  ret = yield result
@@ -754,8 +886,7 @@ module ActiveRecord
754
886
  ret
755
887
  end
756
888
 
757
- def exec_no_cache(sql, name, binds, async: false)
758
- materialize_transactions
889
+ def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
759
890
  mark_transaction_written_if_write(sql)
760
891
 
761
892
  # make sure we carry over any changes to ActiveRecord.default_timezone that have been
@@ -764,23 +895,23 @@ module ActiveRecord
764
895
 
765
896
  type_casted_binds = type_casted_binds(binds)
766
897
  log(sql, name, binds, type_casted_binds, async: async) do
767
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
768
- @connection.exec_params(sql, type_casted_binds)
898
+ with_raw_connection do |conn|
899
+ conn.exec_params(sql, type_casted_binds)
769
900
  end
770
901
  end
771
902
  end
772
903
 
773
- def exec_cache(sql, name, binds, async: false)
774
- materialize_transactions
904
+ def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
775
905
  mark_transaction_written_if_write(sql)
906
+
776
907
  update_typemap_for_default_timezone
777
908
 
778
909
  stmt_key = prepare_statement(sql, binds)
779
910
  type_casted_binds = type_casted_binds(binds)
780
911
 
781
- log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
782
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
783
- @connection.exec_prepared(stmt_key, type_casted_binds)
912
+ with_raw_connection do |conn|
913
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do
914
+ conn.exec_prepared(stmt_key, type_casted_binds)
784
915
  end
785
916
  end
786
917
  rescue ActiveRecord::StatementInvalid => e
@@ -829,17 +960,17 @@ module ActiveRecord
829
960
  # Prepare the statement if it hasn't been prepared, return
830
961
  # the statement key.
831
962
  def prepare_statement(sql, binds)
832
- @lock.synchronize do
963
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |conn|
833
964
  sql_key = sql_key(sql)
834
965
  unless @statements.key? sql_key
835
966
  nextkey = @statements.next_key
836
967
  begin
837
- @connection.prepare nextkey, sql
968
+ conn.prepare nextkey, sql
838
969
  rescue => e
839
970
  raise translate_exception_class(e, sql, binds)
840
971
  end
841
972
  # Clear the queue
842
- @connection.get_last_result
973
+ conn.get_last_result
843
974
  @statements[sql_key] = nextkey
844
975
  end
845
976
  @statements[sql_key]
@@ -849,49 +980,79 @@ module ActiveRecord
849
980
  # Connects to a PostgreSQL server and sets up the adapter depending on the
850
981
  # connected server's characteristics.
851
982
  def connect
852
- @connection = self.class.new_client(@connection_parameters)
853
- configure_connection
854
- add_pg_encoders
855
- add_pg_decoders
983
+ @raw_connection = self.class.new_client(@connection_parameters)
984
+ rescue ConnectionNotEstablished => ex
985
+ raise ex.set_pool(@pool)
986
+ end
987
+
988
+ def reconnect
989
+ begin
990
+ @raw_connection&.reset
991
+ rescue PG::ConnectionBad
992
+ @raw_connection = nil
993
+ end
994
+
995
+ connect unless @raw_connection
856
996
  end
857
997
 
858
998
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
859
999
  # This is called by #connect and should not be called manually.
860
1000
  def configure_connection
861
1001
  if @config[:encoding]
862
- @connection.set_client_encoding(@config[:encoding])
1002
+ @raw_connection.set_client_encoding(@config[:encoding])
863
1003
  end
864
1004
  self.client_min_messages = @config[:min_messages] || "warning"
865
1005
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
866
1006
 
1007
+ unless ActiveRecord.db_warnings_action.nil?
1008
+ @raw_connection.set_notice_receiver do |result|
1009
+ message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY)
1010
+ code = result.error_field(PG::Result::PG_DIAG_SQLSTATE)
1011
+ level = result.error_field(PG::Result::PG_DIAG_SEVERITY)
1012
+ @notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool)
1013
+ end
1014
+ end
1015
+
867
1016
  # Use standard-conforming strings so we don't have to do the E'...' dance.
868
1017
  set_standard_conforming_strings
869
1018
 
870
1019
  variables = @config.fetch(:variables, {}).stringify_keys
871
1020
 
872
- # If using Active Record's time zone support configure the connection to return
873
- # TIMESTAMP WITH ZONE types in UTC.
874
- unless variables["timezone"]
875
- if ActiveRecord.default_timezone == :utc
876
- variables["timezone"] = "UTC"
877
- elsif @local_tz
878
- variables["timezone"] = @local_tz
879
- end
880
- end
881
-
882
1021
  # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
883
- execute("SET intervalstyle = iso_8601", "SCHEMA")
1022
+ internal_execute("SET intervalstyle = iso_8601")
884
1023
 
885
1024
  # SET statements from :variables config hash
886
1025
  # https://www.postgresql.org/docs/current/static/sql-set.html
887
1026
  variables.map do |k, v|
888
1027
  if v == ":default" || v == :default
889
1028
  # Sets the value to the global or compile default
890
- execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
1029
+ internal_execute("SET SESSION #{k} TO DEFAULT")
891
1030
  elsif !v.nil?
892
- execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
1031
+ internal_execute("SET SESSION #{k} TO #{quote(v)}")
893
1032
  end
894
1033
  end
1034
+
1035
+ add_pg_encoders
1036
+ add_pg_decoders
1037
+
1038
+ reload_type_map
1039
+ end
1040
+
1041
+ def reconfigure_connection_timezone
1042
+ variables = @config.fetch(:variables, {}).stringify_keys
1043
+
1044
+ # If it's been directly configured as a connection variable, we don't
1045
+ # need to do anything here; it will be set up by configure_connection
1046
+ # and then never changed.
1047
+ return if variables["timezone"]
1048
+
1049
+ # If using Active Record's time zone support configure the connection
1050
+ # to return TIMESTAMP WITH ZONE types in UTC.
1051
+ if default_timezone == :utc
1052
+ internal_execute("SET SESSION timezone TO 'UTC'")
1053
+ else
1054
+ internal_execute("SET SESSION timezone TO DEFAULT")
1055
+ end
895
1056
  end
896
1057
 
897
1058
  # Returns the list of a table's column names, data types, and default values.
@@ -938,27 +1099,32 @@ module ActiveRecord
938
1099
  end
939
1100
 
940
1101
  def build_statement_pool
941
- StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
1102
+ StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit]))
942
1103
  end
943
1104
 
944
1105
  def can_perform_case_insensitive_comparison_for?(column)
945
- @case_insensitive_cache ||= {}
946
- @case_insensitive_cache[column.sql_type] ||= begin
947
- sql = <<~SQL
948
- SELECT exists(
949
- SELECT * FROM pg_proc
950
- WHERE proname = 'lower'
951
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
952
- ) OR exists(
953
- SELECT * FROM pg_proc
954
- INNER JOIN pg_cast
955
- ON ARRAY[casttarget]::oidvector = proargtypes
956
- WHERE proname = 'lower'
957
- AND castsource = #{quote column.sql_type}::regtype
958
- )
959
- SQL
960
- execute_and_clear(sql, "SCHEMA", []) do |result|
961
- result.getvalue(0, 0)
1106
+ # NOTE: citext is an exception. It is possible to perform a
1107
+ # case-insensitive comparison using `LOWER()`, but it is
1108
+ # unnecessary, as `citext` is case-insensitive by definition.
1109
+ @case_insensitive_cache ||= { "citext" => false }
1110
+ @case_insensitive_cache.fetch(column.sql_type) do
1111
+ @case_insensitive_cache[column.sql_type] = begin
1112
+ sql = <<~SQL
1113
+ SELECT exists(
1114
+ SELECT * FROM pg_proc
1115
+ WHERE proname = 'lower'
1116
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
1117
+ ) OR exists(
1118
+ SELECT * FROM pg_proc
1119
+ INNER JOIN pg_cast
1120
+ ON ARRAY[casttarget]::oidvector = proargtypes
1121
+ WHERE proname = 'lower'
1122
+ AND castsource = #{quote column.sql_type}::regtype
1123
+ )
1124
+ SQL
1125
+ execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1126
+ result.getvalue(0, 0)
1127
+ end
962
1128
  end
963
1129
  end
964
1130
  end
@@ -968,28 +1134,30 @@ module ActiveRecord
968
1134
  map[Integer] = PG::TextEncoder::Integer.new
969
1135
  map[TrueClass] = PG::TextEncoder::Boolean.new
970
1136
  map[FalseClass] = PG::TextEncoder::Boolean.new
971
- @connection.type_map_for_queries = map
1137
+ @raw_connection.type_map_for_queries = map
972
1138
  end
973
1139
 
974
1140
  def update_typemap_for_default_timezone
975
- if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
976
- decoder_class = ActiveRecord.default_timezone == :utc ?
1141
+ if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
1142
+ decoder_class = default_timezone == :utc ?
977
1143
  PG::TextDecoder::TimestampUtc :
978
1144
  PG::TextDecoder::TimestampWithoutTimeZone
979
1145
 
980
1146
  @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
981
- @connection.type_map_for_results.add_coder(@timestamp_decoder)
1147
+ @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
982
1148
 
983
- @default_timezone = ActiveRecord.default_timezone
1149
+ @mapped_default_timezone = default_timezone
984
1150
 
985
1151
  # if default timezone has changed, we need to reconfigure the connection
986
1152
  # (specifically, the session time zone)
987
- configure_connection
1153
+ reconfigure_connection_timezone
1154
+
1155
+ true
988
1156
  end
989
1157
  end
990
1158
 
991
1159
  def add_pg_decoders
992
- @default_timezone = nil
1160
+ @mapped_default_timezone = nil
993
1161
  @timestamp_decoder = nil
994
1162
 
995
1163
  coders_by_name = {
@@ -1011,13 +1179,13 @@ module ActiveRecord
1011
1179
  FROM pg_type as t
1012
1180
  WHERE t.typname IN (%s)
1013
1181
  SQL
1014
- coders = execute_and_clear(query, "SCHEMA", []) do |result|
1182
+ coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1015
1183
  result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1016
1184
  end
1017
1185
 
1018
1186
  map = PG::TypeMapByOid.new
1019
1187
  coders.each { |coder| map.add_coder(coder) }
1020
- @connection.type_map_for_results = map
1188
+ @raw_connection.type_map_for_results = map
1021
1189
 
1022
1190
  @type_map_for_results = PG::TypeMapByOid.new
1023
1191
  @type_map_for_results.default_type_map = map