activerecord 7.0.6 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (229) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1356 -1425
  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 +18 -10
  15. data/lib/active_record/associations/collection_proxy.rb +21 -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 -7
  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 +6 -8
  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 +60 -18
  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 +496 -102
  56. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +207 -113
  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 +23 -144
  60. data/lib/active_record/connection_adapters/mysql/quoting.rb +21 -14
  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 +14 -8
  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 +9 -5
  83. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  84. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +28 -9
  85. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +163 -81
  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 +142 -58
  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 +265 -112
  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 +186 -34
  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 +12 -8
  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 +27 -16
  165. data/lib/active_record/relation/query_attribute.rb +25 -1
  166. data/lib/active_record/relation/query_methods.rb +377 -69
  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 +11 -2
  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 +39 -13
  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/serialized.rb +4 -0
  196. data/lib/active_record/type/time.rb +4 -0
  197. data/lib/active_record/validations/absence.rb +1 -1
  198. data/lib/active_record/validations/numericality.rb +5 -4
  199. data/lib/active_record/validations/presence.rb +5 -28
  200. data/lib/active_record/validations/uniqueness.rb +47 -2
  201. data/lib/active_record/validations.rb +8 -4
  202. data/lib/active_record/version.rb +1 -1
  203. data/lib/active_record.rb +121 -16
  204. data/lib/arel/errors.rb +10 -0
  205. data/lib/arel/factory_methods.rb +4 -0
  206. data/lib/arel/nodes/and.rb +4 -0
  207. data/lib/arel/nodes/binary.rb +6 -1
  208. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  209. data/lib/arel/nodes/cte.rb +36 -0
  210. data/lib/arel/nodes/fragments.rb +35 -0
  211. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  212. data/lib/arel/nodes/leading_join.rb +8 -0
  213. data/lib/arel/nodes/node.rb +111 -2
  214. data/lib/arel/nodes/sql_literal.rb +6 -0
  215. data/lib/arel/nodes/table_alias.rb +4 -0
  216. data/lib/arel/nodes.rb +4 -0
  217. data/lib/arel/predications.rb +2 -0
  218. data/lib/arel/table.rb +9 -5
  219. data/lib/arel/visitors/mysql.rb +8 -1
  220. data/lib/arel/visitors/to_sql.rb +81 -17
  221. data/lib/arel/visitors/visitor.rb +2 -2
  222. data/lib/arel.rb +16 -2
  223. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  224. data/lib/rails/generators/active_record/migration.rb +3 -1
  225. data/lib/rails/generators/active_record/model/USAGE +113 -0
  226. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  227. metadata +52 -17
  228. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  229. 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