activerecord 7.0.8 → 7.1.3.4

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