activerecord 7.0.8.7 → 7.2.3

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