activerecord 6.1.7 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +616 -1290
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +19 -8
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  30. data/lib/active_record/associations/join_dependency.rb +28 -20
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +429 -522
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +1 -5
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +15 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +57 -54
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +19 -35
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +325 -604
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +230 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +378 -143
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +348 -165
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +403 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +520 -253
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +310 -253
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +10 -4
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +58 -0
  165. data/lib/active_record/enum.rb +170 -62
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +59 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +145 -158
  199. data/lib/active_record/nested_attributes.rb +61 -23
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +18 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +229 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +332 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +200 -65
  216. data/lib/active_record/relation/calculations.rb +301 -112
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +870 -163
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +6 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +288 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +65 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/sqlite.rb +25 -0
  317. data/lib/arel/visitors/to_sql.rb +170 -36
  318. data/lib/arel/visitors/visitor.rb +2 -2
  319. data/lib/arel.rb +23 -4
  320. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  321. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  322. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  323. data/lib/rails/generators/active_record/migration.rb +3 -1
  324. data/lib/rails/generators/active_record/model/USAGE +113 -0
  325. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  326. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  328. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  329. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  330. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  331. metadata +103 -17
  332. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  333. data/lib/active_record/null_relation.rb +0 -67
@@ -20,29 +20,9 @@ require "active_record/connection_adapters/postgresql/type_metadata"
20
20
  require "active_record/connection_adapters/postgresql/utils"
21
21
 
22
22
  module ActiveRecord
23
- module ConnectionHandling # :nodoc:
24
- # Establishes a connection to the database that's used by all Active Record objects
25
- 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
- )
42
- end
43
- end
44
-
45
23
  module ConnectionAdapters
24
+ # = Active Record PostgreSQL Adapter
25
+ #
46
26
  # The PostgreSQL adapter works with the native C (https://github.com/ged/ruby-pg) driver.
47
27
  #
48
28
  # Options:
@@ -52,7 +32,7 @@ module ActiveRecord
52
32
  # * <tt>:port</tt> - Defaults to 5432.
53
33
  # * <tt>:username</tt> - Defaults to be the same as the operating system name of the user running the application.
54
34
  # * <tt>:password</tt> - Password to be used if the server demands password authentication.
55
- # * <tt>:database</tt> - Defaults to be the same as the user name.
35
+ # * <tt>:database</tt> - Defaults to be the same as the username.
56
36
  # * <tt>:schema_search_path</tt> - An optional schema search path for the connection given
57
37
  # as a string of comma-separated schema names. This is backward-compatible with the <tt>:schema_order</tt> option.
58
38
  # * <tt>:encoding</tt> - An optional client encoding that is used in a <tt>SET client_encoding TO
@@ -77,12 +57,37 @@ module ActiveRecord
77
57
  def new_client(conn_params)
78
58
  PG.connect(**conn_params)
79
59
  rescue ::PG::Error => error
80
- if conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
81
- raise ActiveRecord::NoDatabaseError
60
+ if conn_params && conn_params[:dbname] == "postgres"
61
+ raise ActiveRecord::ConnectionNotEstablished, error.message
62
+ elsif conn_params && conn_params[:dbname] && error.message.include?(conn_params[:dbname])
63
+ raise ActiveRecord::NoDatabaseError.db_error(conn_params[:dbname])
64
+ elsif conn_params && conn_params[:user] && error.message.include?(conn_params[:user])
65
+ raise ActiveRecord::DatabaseConnectionError.username_error(conn_params[:user])
66
+ elsif conn_params && conn_params[:host] && error.message.include?(conn_params[:host])
67
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(conn_params[:host])
82
68
  else
83
69
  raise ActiveRecord::ConnectionNotEstablished, error.message
84
70
  end
85
71
  end
72
+
73
+ def dbconsole(config, options = {})
74
+ pg_config = config.configuration_hash
75
+
76
+ ENV["PGUSER"] = pg_config[:username] if pg_config[:username]
77
+ ENV["PGHOST"] = pg_config[:host] if pg_config[:host]
78
+ ENV["PGPORT"] = pg_config[:port].to_s if pg_config[:port]
79
+ ENV["PGPASSWORD"] = pg_config[:password].to_s if pg_config[:password] && options[:include_password]
80
+ ENV["PGSSLMODE"] = pg_config[:sslmode].to_s if pg_config[:sslmode]
81
+ ENV["PGSSLCERT"] = pg_config[:sslcert].to_s if pg_config[:sslcert]
82
+ ENV["PGSSLKEY"] = pg_config[:sslkey].to_s if pg_config[:sslkey]
83
+ ENV["PGSSLROOTCERT"] = pg_config[:sslrootcert].to_s if pg_config[:sslrootcert]
84
+ if pg_config[:variables]
85
+ ENV["PGOPTIONS"] = pg_config[:variables].filter_map do |name, value|
86
+ "-c #{name}=#{value.to_s.gsub(/[ \\]/, '\\\\\0')}" unless value == ":default" || value == :default
87
+ end.join(" ")
88
+ end
89
+ find_cmd_and_exec("psql", config.database)
90
+ end
86
91
  end
87
92
 
88
93
  ##
@@ -92,20 +97,51 @@ module ActiveRecord
92
97
  # but significantly increases the risk of data loss if the database
93
98
  # crashes. As a result, this should not be used in production
94
99
  # environments. If you would like all created tables to be unlogged in
95
- # the test environment you can add the following line to your test.rb
96
- # file:
100
+ # the test environment you can add the following to your test.rb file:
97
101
  #
98
- # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.create_unlogged_tables = true
102
+ # ActiveSupport.on_load(:active_record_postgresqladapter) do
103
+ # self.create_unlogged_tables = true
104
+ # end
99
105
  class_attribute :create_unlogged_tables, default: false
100
106
 
107
+ ##
108
+ # :singleton-method:
109
+ # PostgreSQL supports multiple types for DateTimes. By default, if you use +datetime+
110
+ # in migrations, \Rails will translate this to a PostgreSQL "timestamp without time zone".
111
+ # Change this in an initializer to use another NATIVE_DATABASE_TYPES. For example, to
112
+ # store DateTimes as "timestamp with time zone":
113
+ #
114
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :timestamptz
115
+ #
116
+ # Or if you are adding a custom type:
117
+ #
118
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:my_custom_type] = { name: "my_custom_type_name" }
119
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type = :my_custom_type
120
+ #
121
+ # If you're using +:ruby+ as your +config.active_record.schema_format+ and you change this
122
+ # setting, you should immediately run <tt>bin/rails db:migrate</tt> to update the types in your schema.rb.
123
+ class_attribute :datetime_type, default: :timestamp
124
+
125
+ ##
126
+ # :singleton-method:
127
+ # Toggles automatic decoding of date columns.
128
+ #
129
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> String
130
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.decode_dates = true
131
+ # ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.select_value("select '2024-01-01'::date").class #=> Date
132
+ class_attribute :decode_dates, default: false
133
+
101
134
  NATIVE_DATABASE_TYPES = {
102
135
  primary_key: "bigserial primary key",
103
136
  string: { name: "character varying" },
104
137
  text: { name: "text" },
105
138
  integer: { name: "integer", limit: 4 },
139
+ bigint: { name: "bigint" },
106
140
  float: { name: "float" },
107
141
  decimal: { name: "decimal" },
108
- datetime: { name: "timestamp" },
142
+ datetime: {}, # set dynamically based on datetime_type
143
+ timestamp: { name: "timestamp" },
144
+ timestamptz: { name: "timestamptz" },
109
145
  time: { name: "time" },
110
146
  date: { name: "date" },
111
147
  daterange: { name: "daterange" },
@@ -139,9 +175,10 @@ module ActiveRecord
139
175
  money: { name: "money" },
140
176
  interval: { name: "interval" },
141
177
  oid: { name: "oid" },
178
+ enum: {} # special type https://www.postgresql.org/docs/current/datatype-enum.html
142
179
  }
143
180
 
144
- OID = PostgreSQL::OID #:nodoc:
181
+ OID = PostgreSQL::OID # :nodoc:
145
182
 
146
183
  include PostgreSQL::Quoting
147
184
  include PostgreSQL::ReferentialIntegrity
@@ -157,13 +194,17 @@ module ActiveRecord
157
194
  end
158
195
 
159
196
  def supports_partitioned_indexes?
160
- database_version >= 110_000
197
+ database_version >= 11_00_00 # >= 11.0
161
198
  end
162
199
 
163
200
  def supports_partial_index?
164
201
  true
165
202
  end
166
203
 
204
+ def supports_index_include?
205
+ database_version >= 11_00_00 # >= 11.0
206
+ end
207
+
167
208
  def supports_expression_index?
168
209
  true
169
210
  end
@@ -180,10 +221,22 @@ module ActiveRecord
180
221
  true
181
222
  end
182
223
 
224
+ def supports_exclusion_constraints?
225
+ true
226
+ end
227
+
228
+ def supports_unique_constraints?
229
+ true
230
+ end
231
+
183
232
  def supports_validate_constraints?
184
233
  true
185
234
  end
186
235
 
236
+ def supports_deferrable_constraints?
237
+ true
238
+ end
239
+
187
240
  def supports_views?
188
241
  true
189
242
  end
@@ -204,17 +257,33 @@ module ActiveRecord
204
257
  true
205
258
  end
206
259
 
260
+ def supports_restart_db_transaction?
261
+ database_version >= 12_00_00 # >= 12.0
262
+ end
263
+
207
264
  def supports_insert_returning?
208
265
  true
209
266
  end
210
267
 
211
268
  def supports_insert_on_conflict?
212
- database_version >= 90500
269
+ database_version >= 9_05_00 # >= 9.5
213
270
  end
214
271
  alias supports_insert_on_duplicate_skip? supports_insert_on_conflict?
215
272
  alias supports_insert_on_duplicate_update? supports_insert_on_conflict?
216
273
  alias supports_insert_conflict_target? supports_insert_on_conflict?
217
274
 
275
+ def supports_virtual_columns?
276
+ database_version >= 12_00_00 # >= 12.0
277
+ end
278
+
279
+ def supports_identity_columns? # :nodoc:
280
+ database_version >= 10_00_00 # >= 10.0
281
+ end
282
+
283
+ def supports_nulls_not_distinct?
284
+ database_version >= 15_00_00 # >= 15.0
285
+ end
286
+
218
287
  def index_algorithms
219
288
  { concurrently: "CONCURRENTLY" }
220
289
  end
@@ -232,73 +301,78 @@ module ActiveRecord
232
301
 
233
302
  private
234
303
  def dealloc(key)
235
- @connection.query "DEALLOCATE #{key}" if connection_active?
236
- rescue PG::Error
237
- end
238
-
239
- def connection_active?
240
- @connection.status == PG::CONNECTION_OK
304
+ # This is ugly, but safe: the statement pool is only
305
+ # accessed while holding the connection's lock. (And we
306
+ # don't need the complication of with_raw_connection because
307
+ # a reconnect would invalidate the entire statement pool.)
308
+ if conn = @connection.instance_variable_get(:@raw_connection)
309
+ conn.query "DEALLOCATE #{key}" if conn.status == PG::CONNECTION_OK
310
+ end
241
311
  rescue PG::Error
242
- false
243
312
  end
244
313
  end
245
314
 
246
315
  # Initializes and connects a PostgreSQL adapter.
247
- def initialize(connection, logger, connection_parameters, config)
248
- super(connection, logger, config)
316
+ def initialize(...)
317
+ super
249
318
 
250
- @connection_parameters = connection_parameters || {}
319
+ conn_params = @config.compact
251
320
 
252
- # @local_tz is initialized as nil to avoid warnings when connect tries to use it
253
- @local_tz = nil
254
- @max_identifier_length = nil
321
+ # Map ActiveRecords param names to PGs.
322
+ conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
323
+ conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
324
+
325
+ # Forward only valid config params to PG::Connection.connect.
326
+ valid_conn_param_keys = PG::Connection.conndefaults_hash.keys + [:requiressl]
327
+ conn_params.slice!(*valid_conn_param_keys)
328
+
329
+ @connection_parameters = conn_params
255
330
 
256
- configure_connection
257
- add_pg_encoders
258
- add_pg_decoders
331
+ @max_identifier_length = nil
332
+ @type_map = nil
333
+ @raw_connection = nil
334
+ @notice_receiver_sql_warnings = []
259
335
 
260
- @type_map = Type::HashLookupTypeMap.new
261
- initialize_type_map
262
- @local_tz = execute("SHOW TIME ZONE", "SCHEMA").first["TimeZone"]
263
336
  @use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
264
337
  end
265
338
 
266
- def self.database_exists?(config)
267
- !!ActiveRecord::Base.postgresql_connection(config)
268
- rescue ActiveRecord::NoDatabaseError
269
- false
339
+ def connected?
340
+ !(@raw_connection.nil? || @raw_connection.finished?)
270
341
  end
271
342
 
272
343
  # Is this connection alive and ready for queries?
273
344
  def active?
274
345
  @lock.synchronize do
275
- @connection.query "SELECT 1"
346
+ return false unless @raw_connection
347
+ @raw_connection.query ";"
276
348
  end
277
349
  true
278
350
  rescue PG::Error
279
351
  false
280
352
  end
281
353
 
282
- # Close then reopen the connection.
283
- def reconnect!
354
+ def reload_type_map # :nodoc:
284
355
  @lock.synchronize do
285
- super
286
- @connection.reset
287
- configure_connection
288
- rescue PG::ConnectionBad
289
- connect
356
+ if @type_map
357
+ type_map.clear
358
+ else
359
+ @type_map = Type::HashLookupTypeMap.new
360
+ end
361
+
362
+ initialize_type_map
290
363
  end
291
364
  end
292
365
 
293
366
  def reset!
294
367
  @lock.synchronize do
295
- clear_cache!
296
- reset_transaction
297
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
298
- @connection.query "ROLLBACK"
368
+ return connect! unless @raw_connection
369
+
370
+ unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
371
+ @raw_connection.query "ROLLBACK"
299
372
  end
300
- @connection.query "DISCARD ALL"
301
- configure_connection
373
+ @raw_connection.query "DISCARD ALL"
374
+
375
+ super
302
376
  end
303
377
  end
304
378
 
@@ -307,22 +381,31 @@ module ActiveRecord
307
381
  def disconnect!
308
382
  @lock.synchronize do
309
383
  super
310
- @connection.close rescue nil
384
+ @raw_connection&.close rescue nil
385
+ @raw_connection = nil
311
386
  end
312
387
  end
313
388
 
314
389
  def discard! # :nodoc:
315
390
  super
316
- @connection.socket_io.reopen(IO::NULL) rescue nil
317
- @connection = nil
391
+ @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
392
+ @raw_connection = nil
393
+ end
394
+
395
+ def native_database_types # :nodoc:
396
+ self.class.native_database_types
318
397
  end
319
398
 
320
- def native_database_types #:nodoc:
321
- NATIVE_DATABASE_TYPES
399
+ def self.native_database_types # :nodoc:
400
+ @native_database_types ||= begin
401
+ types = NATIVE_DATABASE_TYPES.dup
402
+ types[:datetime] = types[datetime_type]
403
+ types
404
+ end
322
405
  end
323
406
 
324
407
  def set_standard_conforming_strings
325
- execute("SET standard_conforming_strings = on", "SCHEMA")
408
+ internal_execute("SET standard_conforming_strings = on")
326
409
  end
327
410
 
328
411
  def supports_ddl_transactions?
@@ -350,7 +433,7 @@ module ActiveRecord
350
433
  end
351
434
 
352
435
  def supports_pgcrypto_uuid?
353
- database_version >= 90400
436
+ database_version >= 9_04_00 # >= 9.4
354
437
  end
355
438
 
356
439
  def supports_optimizer_hints?
@@ -382,14 +465,21 @@ module ActiveRecord
382
465
  query_value("SELECT pg_advisory_unlock(#{lock_id})")
383
466
  end
384
467
 
385
- def enable_extension(name)
386
- exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
387
- reload_type_map
388
- }
468
+ def enable_extension(name, **)
469
+ schema, name = name.to_s.split(".").values_at(-2, -1)
470
+ sql = +"CREATE EXTENSION IF NOT EXISTS \"#{name}\""
471
+ sql << " SCHEMA #{schema}" if schema
472
+
473
+ internal_exec_query(sql).tap { reload_type_map }
389
474
  end
390
475
 
391
- def disable_extension(name)
392
- exec_query("DROP EXTENSION IF EXISTS \"#{name}\" CASCADE").tap {
476
+ # Removes an extension from the database.
477
+ #
478
+ # [<tt>:force</tt>]
479
+ # Set to +:cascade+ to drop dependent objects as well.
480
+ # Defaults to false.
481
+ def disable_extension(name, force: false)
482
+ internal_exec_query("DROP EXTENSION IF EXISTS \"#{name}\"#{' CASCADE' if force == :cascade}").tap {
393
483
  reload_type_map
394
484
  }
395
485
  end
@@ -403,7 +493,105 @@ module ActiveRecord
403
493
  end
404
494
 
405
495
  def extensions
406
- exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values
496
+ internal_exec_query("SELECT extname FROM pg_extension", "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values
497
+ end
498
+
499
+ # Returns a list of defined enum types, and their values.
500
+ def enum_types
501
+ query = <<~SQL
502
+ SELECT
503
+ type.typname AS name,
504
+ type.OID AS oid,
505
+ n.nspname AS schema,
506
+ string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
507
+ FROM pg_enum AS enum
508
+ JOIN pg_type AS type ON (type.oid = enum.enumtypid)
509
+ JOIN pg_namespace n ON type.typnamespace = n.oid
510
+ WHERE n.nspname = ANY (current_schemas(false))
511
+ GROUP BY type.OID, n.nspname, type.typname;
512
+ SQL
513
+
514
+ internal_exec_query(query, "SCHEMA", allow_retry: true, materialize_transactions: false).cast_values.each_with_object({}) do |row, memo|
515
+ name, schema = row[0], row[2]
516
+ schema = nil if schema == current_schema
517
+ full_name = [schema, name].compact.join(".")
518
+ memo[full_name] = row.last
519
+ end.to_a
520
+ end
521
+
522
+ # Given a name and an array of values, creates an enum type.
523
+ def create_enum(name, values, **options)
524
+ sql_values = values.map { |s| quote(s) }.join(", ")
525
+ scope = quoted_scope(name)
526
+ query = <<~SQL
527
+ DO $$
528
+ BEGIN
529
+ IF NOT EXISTS (
530
+ SELECT 1
531
+ FROM pg_type t
532
+ JOIN pg_namespace n ON t.typnamespace = n.oid
533
+ WHERE t.typname = #{scope[:name]}
534
+ AND n.nspname = #{scope[:schema]}
535
+ ) THEN
536
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
537
+ END IF;
538
+ END
539
+ $$;
540
+ SQL
541
+ internal_exec_query(query).tap { reload_type_map }
542
+ end
543
+
544
+ # Drops an enum type.
545
+ #
546
+ # If the <tt>if_exists: true</tt> option is provided, the enum is dropped
547
+ # only if it exists. Otherwise, if the enum doesn't exist, an error is
548
+ # raised.
549
+ #
550
+ # The +values+ parameter will be ignored if present. It can be helpful
551
+ # to provide this in a migration's +change+ method so it can be reverted.
552
+ # In that case, +values+ will be used by #create_enum.
553
+ def drop_enum(name, values = nil, **options)
554
+ query = <<~SQL
555
+ DROP TYPE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(name)};
556
+ SQL
557
+ internal_exec_query(query).tap { reload_type_map }
558
+ end
559
+
560
+ # Rename an existing enum type to something else.
561
+ def rename_enum(name, options = {})
562
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
563
+
564
+ exec_query("ALTER TYPE #{quote_table_name(name)} RENAME TO #{to}").tap { reload_type_map }
565
+ end
566
+
567
+ # Add enum value to an existing enum type.
568
+ def add_enum_value(type_name, value, options = {})
569
+ before, after = options.values_at(:before, :after)
570
+ sql = +"ALTER TYPE #{quote_table_name(type_name)} ADD VALUE '#{value}'"
571
+
572
+ if before && after
573
+ raise ArgumentError, "Cannot have both :before and :after at the same time"
574
+ elsif before
575
+ sql << " BEFORE '#{before}'"
576
+ elsif after
577
+ sql << " AFTER '#{after}'"
578
+ end
579
+
580
+ execute(sql).tap { reload_type_map }
581
+ end
582
+
583
+ # Rename enum value on an existing enum type.
584
+ def rename_enum_value(type_name, options = {})
585
+ unless database_version >= 10_00_00 # >= 10.0
586
+ raise ArgumentError, "Renaming enum values is only supported in PostgreSQL 10 or later"
587
+ end
588
+
589
+ from = options.fetch(:from) { raise ArgumentError, ":from is required" }
590
+ to = options.fetch(:to) { raise ArgumentError, ":to is required" }
591
+
592
+ execute("ALTER TYPE #{quote_table_name(type_name)} RENAME VALUE '#{from}' TO '#{to}'").tap {
593
+ reload_type_map
594
+ }
407
595
  end
408
596
 
409
597
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -414,7 +602,7 @@ module ActiveRecord
414
602
  # Set the authorized user for this session
415
603
  def session_auth=(user)
416
604
  clear_cache!
417
- execute("SET SESSION AUTHORIZATION #{user}")
605
+ internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
418
606
  end
419
607
 
420
608
  def use_insert_returning?
@@ -423,7 +611,9 @@ module ActiveRecord
423
611
 
424
612
  # Returns the version of the connected PostgreSQL server.
425
613
  def get_database_version # :nodoc:
426
- @connection.server_version
614
+ with_raw_connection do |conn|
615
+ conn.server_version
616
+ end
427
617
  end
428
618
  alias :postgresql_version :database_version
429
619
 
@@ -438,8 +628,12 @@ module ActiveRecord
438
628
  sql << " ON CONFLICT #{insert.conflict_target} DO NOTHING"
439
629
  elsif insert.update_duplicates?
440
630
  sql << " ON CONFLICT #{insert.conflict_target} DO UPDATE SET "
441
- sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
442
- sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
631
+ if insert.raw_update_sql?
632
+ sql << insert.raw_update_sql
633
+ else
634
+ sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column} IS NOT DISTINCT FROM excluded.#{column}" }
635
+ sql << insert.updatable_columns.map { |column| "#{column}=excluded.#{column}" }.join(",")
636
+ end
443
637
  end
444
638
 
445
639
  sql << " RETURNING #{insert.returning}" if insert.returning
@@ -447,79 +641,19 @@ module ActiveRecord
447
641
  end
448
642
 
449
643
  def check_version # :nodoc:
450
- if database_version < 90300
644
+ if database_version < 9_03_00 # < 9.3
451
645
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
452
646
  end
453
647
  end
454
648
 
455
- private
456
- # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
457
- VALUE_LIMIT_VIOLATION = "22001"
458
- NUMERIC_VALUE_OUT_OF_RANGE = "22003"
459
- NOT_NULL_VIOLATION = "23502"
460
- FOREIGN_KEY_VIOLATION = "23503"
461
- UNIQUE_VIOLATION = "23505"
462
- SERIALIZATION_FAILURE = "40001"
463
- DEADLOCK_DETECTED = "40P01"
464
- DUPLICATE_DATABASE = "42P04"
465
- LOCK_NOT_AVAILABLE = "55P03"
466
- QUERY_CANCELED = "57014"
467
-
468
- def translate_exception(exception, message:, sql:, binds:)
469
- return exception unless exception.respond_to?(:result)
470
-
471
- case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
472
- when nil
473
- if exception.message.match?(/connection is closed/i)
474
- ConnectionNotEstablished.new(exception)
475
- else
476
- super
477
- end
478
- when UNIQUE_VIOLATION
479
- RecordNotUnique.new(message, sql: sql, binds: binds)
480
- when FOREIGN_KEY_VIOLATION
481
- InvalidForeignKey.new(message, sql: sql, binds: binds)
482
- when VALUE_LIMIT_VIOLATION
483
- ValueTooLong.new(message, sql: sql, binds: binds)
484
- when NUMERIC_VALUE_OUT_OF_RANGE
485
- RangeError.new(message, sql: sql, binds: binds)
486
- when NOT_NULL_VIOLATION
487
- NotNullViolation.new(message, sql: sql, binds: binds)
488
- when SERIALIZATION_FAILURE
489
- SerializationFailure.new(message, sql: sql, binds: binds)
490
- when DEADLOCK_DETECTED
491
- Deadlocked.new(message, sql: sql, binds: binds)
492
- when DUPLICATE_DATABASE
493
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
494
- when LOCK_NOT_AVAILABLE
495
- LockWaitTimeout.new(message, sql: sql, binds: binds)
496
- when QUERY_CANCELED
497
- QueryCanceled.new(message, sql: sql, binds: binds)
498
- else
499
- super
500
- end
501
- end
502
-
503
- def get_oid_type(oid, fmod, column_name, sql_type = "")
504
- if !type_map.key?(oid)
505
- load_additional_types([oid])
506
- end
507
-
508
- type_map.fetch(oid, fmod, sql_type) {
509
- warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
510
- Type.default_value.tap do |cast_type|
511
- type_map.register_type(oid, cast_type)
512
- end
513
- }
514
- end
515
-
516
- def initialize_type_map(m = type_map)
649
+ class << self
650
+ def initialize_type_map(m) # :nodoc:
517
651
  m.register_type "int2", Type::Integer.new(limit: 2)
518
652
  m.register_type "int4", Type::Integer.new(limit: 4)
519
653
  m.register_type "int8", Type::Integer.new(limit: 8)
520
654
  m.register_type "oid", OID::Oid.new
521
- m.register_type "float4", Type::Float.new
522
- m.alias_type "float8", "float4"
655
+ m.register_type "float4", Type::Float.new(limit: 24)
656
+ m.register_type "float8", Type::Float.new
523
657
  m.register_type "text", Type::Text.new
524
658
  register_class_with_limit m, "varchar", Type::String
525
659
  m.alias_type "char", "varchar"
@@ -528,7 +662,6 @@ module ActiveRecord
528
662
  m.register_type "bool", Type::Boolean.new
529
663
  register_class_with_limit m, "bit", OID::Bit
530
664
  register_class_with_limit m, "varbit", OID::BitVarying
531
- m.alias_type "timestamptz", "timestamp"
532
665
  m.register_type "date", OID::Date.new
533
666
 
534
667
  m.register_type "money", OID::Money.new
@@ -552,9 +685,6 @@ module ActiveRecord
552
685
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
553
686
  m.register_type "circle", OID::SpecializedString.new(:circle)
554
687
 
555
- register_class_with_precision m, "time", Type::Time
556
- register_class_with_precision m, "timestamp", OID::DateTime
557
-
558
688
  m.register_type "numeric" do |_, fmod, sql_type|
559
689
  precision = extract_precision(sql_type)
560
690
  scale = extract_scale(sql_type)
@@ -579,6 +709,18 @@ module ActiveRecord
579
709
  precision = extract_precision(sql_type)
580
710
  OID::Interval.new(precision: precision)
581
711
  end
712
+ end
713
+ end
714
+
715
+ private
716
+ attr_reader :type_map
717
+
718
+ def initialize_type_map(m = type_map)
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
582
724
 
583
725
  load_additional_types
584
726
  end
@@ -587,7 +729,7 @@ module ActiveRecord
587
729
  def extract_value_from_default(default)
588
730
  case default
589
731
  # Quoted types
590
- when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
732
+ when /\A[(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
591
733
  # The default 'now'::date is CURRENT_DATE
592
734
  if $1 == "now" && $2 == "date"
593
735
  nil
@@ -618,37 +760,118 @@ module ActiveRecord
618
760
  !default_value && %r{\w+\(.*\)|\(.*\)::\w+|CURRENT_DATE|CURRENT_TIMESTAMP}.match?(default)
619
761
  end
620
762
 
763
+ # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html
764
+ VALUE_LIMIT_VIOLATION = "22001"
765
+ NUMERIC_VALUE_OUT_OF_RANGE = "22003"
766
+ NOT_NULL_VIOLATION = "23502"
767
+ FOREIGN_KEY_VIOLATION = "23503"
768
+ UNIQUE_VIOLATION = "23505"
769
+ SERIALIZATION_FAILURE = "40001"
770
+ DEADLOCK_DETECTED = "40P01"
771
+ DUPLICATE_DATABASE = "42P04"
772
+ LOCK_NOT_AVAILABLE = "55P03"
773
+ QUERY_CANCELED = "57014"
774
+
775
+ def translate_exception(exception, message:, sql:, binds:)
776
+ return exception unless exception.respond_to?(:result)
777
+
778
+ case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
779
+ when nil
780
+ if exception.message.match?(/connection is closed/i) || exception.message.match?(/no connection to the server/i)
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
793
+ else
794
+ super
795
+ end
796
+ when UNIQUE_VIOLATION
797
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
798
+ when FOREIGN_KEY_VIOLATION
799
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
800
+ when VALUE_LIMIT_VIOLATION
801
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
802
+ when NUMERIC_VALUE_OUT_OF_RANGE
803
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
804
+ when NOT_NULL_VIOLATION
805
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
806
+ when SERIALIZATION_FAILURE
807
+ SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
808
+ when DEADLOCK_DETECTED
809
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
810
+ when DUPLICATE_DATABASE
811
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
812
+ when LOCK_NOT_AVAILABLE
813
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
814
+ when QUERY_CANCELED
815
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
816
+ else
817
+ super
818
+ end
819
+ end
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
+
828
+ def get_oid_type(oid, fmod, column_name, sql_type = "")
829
+ if !type_map.key?(oid)
830
+ load_additional_types([oid])
831
+ end
832
+
833
+ type_map.fetch(oid, fmod, sql_type) {
834
+ warn "unknown OID #{oid}: failed to recognize type of '#{column_name}'. It will be treated as String."
835
+ Type.default_value.tap do |cast_type|
836
+ type_map.register_type(oid, cast_type)
837
+ end
838
+ }
839
+ end
840
+
621
841
  def load_additional_types(oids = nil)
622
842
  initializer = OID::TypeMapInitializer.new(type_map)
843
+ load_types_queries(initializer, oids) do |query|
844
+ execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
845
+ initializer.run(records)
846
+ end
847
+ end
848
+ end
623
849
 
850
+ def load_types_queries(initializer, oids)
624
851
  query = <<~SQL
625
852
  SELECT t.oid, t.typname, t.typelem, t.typdelim, t.typinput, r.rngsubtype, t.typtype, t.typbasetype
626
853
  FROM pg_type as t
627
854
  LEFT JOIN pg_range as r ON oid = rngtypid
628
855
  SQL
629
-
630
856
  if oids
631
- query += "WHERE t.oid IN (%s)" % oids.join(", ")
857
+ yield query + "WHERE t.oid IN (%s)" % oids.join(", ")
632
858
  else
633
- query += initializer.query_conditions_for_initial_load
634
- end
635
-
636
- execute_and_clear(query, "SCHEMA", []) do |records|
637
- initializer.run(records)
859
+ yield query + initializer.query_conditions_for_known_type_names
860
+ yield query + initializer.query_conditions_for_known_type_types
861
+ yield query + initializer.query_conditions_for_array_types
638
862
  end
639
863
  end
640
864
 
641
- FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
865
+ FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
642
866
 
643
- def execute_and_clear(sql, name, binds, prepare: false)
644
- if preventing_writes? && write_query?(sql)
645
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
646
- end
867
+ def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
868
+ sql = transform_query(sql)
869
+ check_if_write_query(sql)
647
870
 
648
871
  if !prepare || without_prepared_statement?(binds)
649
- result = exec_no_cache(sql, name, binds)
872
+ result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
650
873
  else
651
- result = exec_cache(sql, name, binds)
874
+ result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
652
875
  end
653
876
  begin
654
877
  ret = yield result
@@ -658,33 +881,38 @@ module ActiveRecord
658
881
  ret
659
882
  end
660
883
 
661
- def exec_no_cache(sql, name, binds)
662
- materialize_transactions
884
+ def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
663
885
  mark_transaction_written_if_write(sql)
664
886
 
665
- # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
887
+ # make sure we carry over any changes to ActiveRecord.default_timezone that have been
666
888
  # made since we established the connection
667
889
  update_typemap_for_default_timezone
668
890
 
669
891
  type_casted_binds = type_casted_binds(binds)
670
- log(sql, name, binds, type_casted_binds) do
671
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
672
- @connection.exec_params(sql, type_casted_binds)
892
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
893
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
894
+ result = conn.exec_params(sql, type_casted_binds)
895
+ verified!
896
+ notification_payload[:row_count] = result.count
897
+ result
673
898
  end
674
899
  end
675
900
  end
676
901
 
677
- def exec_cache(sql, name, binds)
678
- materialize_transactions
902
+ def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
679
903
  mark_transaction_written_if_write(sql)
904
+
680
905
  update_typemap_for_default_timezone
681
906
 
682
- stmt_key = prepare_statement(sql, binds)
683
- type_casted_binds = type_casted_binds(binds)
907
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
908
+ stmt_key = prepare_statement(sql, binds, conn)
909
+ type_casted_binds = type_casted_binds(binds)
684
910
 
685
- log(sql, name, binds, type_casted_binds, stmt_key) do
686
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
687
- @connection.exec_prepared(stmt_key, type_casted_binds)
911
+ log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
912
+ result = conn.exec_prepared(stmt_key, type_casted_binds)
913
+ verified!
914
+ notification_payload[:row_count] = result.count
915
+ result
688
916
  end
689
917
  end
690
918
  rescue ActiveRecord::StatementInvalid => e
@@ -693,7 +921,7 @@ module ActiveRecord
693
921
  # Nothing we can do if we are in a transaction because all commands
694
922
  # will raise InFailedSQLTransaction
695
923
  if in_transaction?
696
- raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
924
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
697
925
  else
698
926
  @lock.synchronize do
699
927
  # outside of transactions we can simply flush this query and retry
@@ -732,70 +960,100 @@ module ActiveRecord
732
960
 
733
961
  # Prepare the statement if it hasn't been prepared, return
734
962
  # the statement key.
735
- def prepare_statement(sql, binds)
736
- @lock.synchronize do
737
- sql_key = sql_key(sql)
738
- unless @statements.key? sql_key
739
- nextkey = @statements.next_key
740
- begin
741
- @connection.prepare nextkey, sql
742
- rescue => e
743
- raise translate_exception_class(e, sql, binds)
744
- end
745
- # Clear the queue
746
- @connection.get_last_result
747
- @statements[sql_key] = nextkey
963
+ def prepare_statement(sql, binds, conn)
964
+ sql_key = sql_key(sql)
965
+ unless @statements.key? sql_key
966
+ nextkey = @statements.next_key
967
+ begin
968
+ conn.prepare nextkey, sql
969
+ rescue => e
970
+ raise translate_exception_class(e, sql, binds)
748
971
  end
749
- @statements[sql_key]
972
+ # Clear the queue
973
+ conn.get_last_result
974
+ @statements[sql_key] = nextkey
750
975
  end
976
+ @statements[sql_key]
751
977
  end
752
978
 
753
979
  # Connects to a PostgreSQL server and sets up the adapter depending on the
754
980
  # connected server's characteristics.
755
981
  def connect
756
- @connection = self.class.new_client(@connection_parameters)
757
- configure_connection
758
- add_pg_encoders
759
- add_pg_decoders
982
+ @raw_connection = self.class.new_client(@connection_parameters)
983
+ rescue ConnectionNotEstablished => ex
984
+ raise ex.set_pool(@pool)
985
+ end
986
+
987
+ def reconnect
988
+ begin
989
+ @raw_connection&.reset
990
+ rescue PG::ConnectionBad
991
+ @raw_connection = nil
992
+ end
993
+
994
+ connect unless @raw_connection
760
995
  end
761
996
 
762
997
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
763
998
  # This is called by #connect and should not be called manually.
764
999
  def configure_connection
1000
+ super
1001
+
765
1002
  if @config[:encoding]
766
- @connection.set_client_encoding(@config[:encoding])
1003
+ @raw_connection.set_client_encoding(@config[:encoding])
767
1004
  end
768
1005
  self.client_min_messages = @config[:min_messages] || "warning"
769
1006
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
770
1007
 
1008
+ unless ActiveRecord.db_warnings_action.nil?
1009
+ @raw_connection.set_notice_receiver do |result|
1010
+ message = result.error_field(PG::Result::PG_DIAG_MESSAGE_PRIMARY)
1011
+ code = result.error_field(PG::Result::PG_DIAG_SQLSTATE)
1012
+ level = result.error_field(PG::Result::PG_DIAG_SEVERITY)
1013
+ @notice_receiver_sql_warnings << SQLWarning.new(message, code, level, nil, @pool)
1014
+ end
1015
+ end
1016
+
771
1017
  # Use standard-conforming strings so we don't have to do the E'...' dance.
772
1018
  set_standard_conforming_strings
773
1019
 
774
1020
  variables = @config.fetch(:variables, {}).stringify_keys
775
1021
 
776
- # If using Active Record's time zone support configure the connection to return
777
- # TIMESTAMP WITH ZONE types in UTC.
778
- unless variables["timezone"]
779
- if ActiveRecord::Base.default_timezone == :utc
780
- variables["timezone"] = "UTC"
781
- elsif @local_tz
782
- variables["timezone"] = @local_tz
783
- end
784
- end
785
-
786
1022
  # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
787
- execute("SET intervalstyle = iso_8601", "SCHEMA")
1023
+ internal_execute("SET intervalstyle = iso_8601")
788
1024
 
789
1025
  # SET statements from :variables config hash
790
1026
  # https://www.postgresql.org/docs/current/static/sql-set.html
791
1027
  variables.map do |k, v|
792
1028
  if v == ":default" || v == :default
793
1029
  # Sets the value to the global or compile default
794
- execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
1030
+ internal_execute("SET SESSION #{k} TO DEFAULT")
795
1031
  elsif !v.nil?
796
- execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
1032
+ internal_execute("SET SESSION #{k} TO #{quote(v)}")
797
1033
  end
798
1034
  end
1035
+
1036
+ add_pg_encoders
1037
+ add_pg_decoders
1038
+
1039
+ reload_type_map
1040
+ end
1041
+
1042
+ def reconfigure_connection_timezone
1043
+ variables = @config.fetch(:variables, {}).stringify_keys
1044
+
1045
+ # If it's been directly configured as a connection variable, we don't
1046
+ # need to do anything here; it will be set up by configure_connection
1047
+ # and then never changed.
1048
+ return if variables["timezone"]
1049
+
1050
+ # If using Active Record's time zone support configure the connection
1051
+ # to return TIMESTAMP WITH ZONE types in UTC.
1052
+ if default_timezone == :utc
1053
+ internal_execute("SET SESSION timezone TO 'UTC'")
1054
+ else
1055
+ internal_execute("SET SESSION timezone TO DEFAULT")
1056
+ end
799
1057
  end
800
1058
 
801
1059
  # Returns the list of a table's column names, data types, and default values.
@@ -820,7 +1078,9 @@ module ActiveRecord
820
1078
  query(<<~SQL, "SCHEMA")
821
1079
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
822
1080
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
823
- c.collname, col_description(a.attrelid, a.attnum) AS comment
1081
+ c.collname, col_description(a.attrelid, a.attnum) AS comment,
1082
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
1083
+ #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
824
1084
  FROM pg_attribute a
825
1085
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
826
1086
  LEFT JOIN pg_type t ON a.atttypid = t.oid
@@ -831,37 +1091,37 @@ module ActiveRecord
831
1091
  SQL
832
1092
  end
833
1093
 
834
- def extract_table_ref_from_insert_sql(sql)
835
- sql[/into\s("[A-Za-z0-9_."\[\]\s]+"|[A-Za-z0-9_."\[\]]+)\s*/im]
836
- $1.strip if $1
837
- end
838
-
839
1094
  def arel_visitor
840
1095
  Arel::Visitors::PostgreSQL.new(self)
841
1096
  end
842
1097
 
843
1098
  def build_statement_pool
844
- StatementPool.new(@connection, self.class.type_cast_config_to_integer(@config[:statement_limit]))
1099
+ StatementPool.new(self, self.class.type_cast_config_to_integer(@config[:statement_limit]))
845
1100
  end
846
1101
 
847
1102
  def can_perform_case_insensitive_comparison_for?(column)
848
- @case_insensitive_cache ||= {}
849
- @case_insensitive_cache[column.sql_type] ||= begin
850
- sql = <<~SQL
851
- SELECT exists(
852
- SELECT * FROM pg_proc
853
- WHERE proname = 'lower'
854
- AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
855
- ) OR exists(
856
- SELECT * FROM pg_proc
857
- INNER JOIN pg_cast
858
- ON ARRAY[casttarget]::oidvector = proargtypes
859
- WHERE proname = 'lower'
860
- AND castsource = #{quote column.sql_type}::regtype
861
- )
862
- SQL
863
- execute_and_clear(sql, "SCHEMA", []) do |result|
864
- result.getvalue(0, 0)
1103
+ # NOTE: citext is an exception. It is possible to perform a
1104
+ # case-insensitive comparison using `LOWER()`, but it is
1105
+ # unnecessary, as `citext` is case-insensitive by definition.
1106
+ @case_insensitive_cache ||= { "citext" => false }
1107
+ @case_insensitive_cache.fetch(column.sql_type) do
1108
+ @case_insensitive_cache[column.sql_type] = begin
1109
+ sql = <<~SQL
1110
+ SELECT exists(
1111
+ SELECT * FROM pg_proc
1112
+ WHERE proname = 'lower'
1113
+ AND proargtypes = ARRAY[#{quote column.sql_type}::regtype]::oidvector
1114
+ ) OR exists(
1115
+ SELECT * FROM pg_proc
1116
+ INNER JOIN pg_cast
1117
+ ON ARRAY[casttarget]::oidvector = proargtypes
1118
+ WHERE proname = 'lower'
1119
+ AND castsource = #{quote column.sql_type}::regtype
1120
+ )
1121
+ SQL
1122
+ execute_and_clear(sql, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1123
+ result.getvalue(0, 0)
1124
+ end
865
1125
  end
866
1126
  end
867
1127
  end
@@ -871,23 +1131,30 @@ module ActiveRecord
871
1131
  map[Integer] = PG::TextEncoder::Integer.new
872
1132
  map[TrueClass] = PG::TextEncoder::Boolean.new
873
1133
  map[FalseClass] = PG::TextEncoder::Boolean.new
874
- @connection.type_map_for_queries = map
1134
+ @raw_connection.type_map_for_queries = map
875
1135
  end
876
1136
 
877
1137
  def update_typemap_for_default_timezone
878
- if @default_timezone != ActiveRecord::Base.default_timezone && @timestamp_decoder
879
- decoder_class = ActiveRecord::Base.default_timezone == :utc ?
1138
+ if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
1139
+ decoder_class = default_timezone == :utc ?
880
1140
  PG::TextDecoder::TimestampUtc :
881
1141
  PG::TextDecoder::TimestampWithoutTimeZone
882
1142
 
883
- @timestamp_decoder = decoder_class.new(@timestamp_decoder.to_h)
884
- @connection.type_map_for_results.add_coder(@timestamp_decoder)
885
- @default_timezone = ActiveRecord::Base.default_timezone
1143
+ @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
1144
+ @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
1145
+
1146
+ @mapped_default_timezone = default_timezone
1147
+
1148
+ # if default timezone has changed, we need to reconfigure the connection
1149
+ # (specifically, the session time zone)
1150
+ reconfigure_connection_timezone
1151
+
1152
+ true
886
1153
  end
887
1154
  end
888
1155
 
889
1156
  def add_pg_decoders
890
- @default_timezone = nil
1157
+ @mapped_default_timezone = nil
891
1158
  @timestamp_decoder = nil
892
1159
 
893
1160
  coders_by_name = {
@@ -902,6 +1169,7 @@ module ActiveRecord
902
1169
  "timestamp" => PG::TextDecoder::TimestampUtc,
903
1170
  "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
904
1171
  }
1172
+ coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
905
1173
 
906
1174
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
907
1175
  query = <<~SQL % known_coder_types.join(", ")
@@ -909,15 +1177,13 @@ module ActiveRecord
909
1177
  FROM pg_type as t
910
1178
  WHERE t.typname IN (%s)
911
1179
  SQL
912
- coders = execute_and_clear(query, "SCHEMA", []) do |result|
913
- result
914
- .map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
915
- .compact
1180
+ coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1181
+ result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
916
1182
  end
917
1183
 
918
1184
  map = PG::TypeMapByOid.new
919
1185
  coders.each { |coder| map.add_coder(coder) }
920
- @connection.type_map_for_results = map
1186
+ @raw_connection.type_map_for_results = map
921
1187
 
922
1188
  @type_map_for_results = PG::TypeMapByOid.new
923
1189
  @type_map_for_results.default_type_map = map
@@ -963,5 +1229,6 @@ module ActiveRecord
963
1229
  ActiveRecord::Type.register(:vector, OID::Vector, adapter: :postgresql)
964
1230
  ActiveRecord::Type.register(:xml, OID::Xml, adapter: :postgresql)
965
1231
  end
1232
+ ActiveSupport.run_load_hooks(:active_record_postgresqladapter, PostgreSQLAdapter)
966
1233
  end
967
1234
  end