activerecord 7.0.8.1 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  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 +25 -19
  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 +23 -8
  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 +26 -14
  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 +30 -27
  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 +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  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 +323 -88
  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 +217 -63
  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 +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  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 +53 -54
  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 +101 -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 +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  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 +45 -46
  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 +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  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 +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -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 +39 -10
  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 +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  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 +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  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 +29 -8
  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 +3 -3
  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 +234 -117
  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 +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  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 +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  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 +18 -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 +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  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 +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  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 +63 -14
  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 +27 -6
  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 +16 -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 +106 -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 +2 -0
  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/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -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:
@@ -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,50 @@ 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 ";"
310
348
  end
311
349
  true
312
350
  rescue PG::Error
@@ -314,31 +352,27 @@ module ActiveRecord
314
352
  end
315
353
 
316
354
  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
355
  @lock.synchronize do
324
- super
325
- @connection.reset
326
- configure_connection
327
- reload_type_map
328
- rescue PG::ConnectionBad
329
- connect
356
+ if @type_map
357
+ type_map.clear
358
+ else
359
+ @type_map = Type::HashLookupTypeMap.new
360
+ end
361
+
362
+ initialize_type_map
330
363
  end
331
364
  end
332
365
 
333
366
  def reset!
334
367
  @lock.synchronize do
335
- clear_cache!
336
- reset_transaction
337
- unless @connection.transaction_status == ::PG::PQTRANS_IDLE
338
- @connection.query "ROLLBACK"
368
+ return connect! unless @raw_connection
369
+
370
+ unless @raw_connection.transaction_status == ::PG::PQTRANS_IDLE
371
+ @raw_connection.query "ROLLBACK"
339
372
  end
340
- @connection.query "DISCARD ALL"
341
- configure_connection
373
+ @raw_connection.query "DISCARD ALL"
374
+
375
+ super
342
376
  end
343
377
  end
344
378
 
@@ -347,14 +381,15 @@ module ActiveRecord
347
381
  def disconnect!
348
382
  @lock.synchronize do
349
383
  super
350
- @connection.close rescue nil
384
+ @raw_connection&.close rescue nil
385
+ @raw_connection = nil
351
386
  end
352
387
  end
353
388
 
354
389
  def discard! # :nodoc:
355
390
  super
356
- @connection.socket_io.reopen(IO::NULL) rescue nil
357
- @connection = nil
391
+ @raw_connection&.socket_io&.reopen(IO::NULL) rescue nil
392
+ @raw_connection = nil
358
393
  end
359
394
 
360
395
  def native_database_types # :nodoc:
@@ -370,7 +405,7 @@ module ActiveRecord
370
405
  end
371
406
 
372
407
  def set_standard_conforming_strings
373
- execute("SET standard_conforming_strings = on", "SCHEMA")
408
+ internal_execute("SET standard_conforming_strings = on")
374
409
  end
375
410
 
376
411
  def supports_ddl_transactions?
@@ -398,7 +433,7 @@ module ActiveRecord
398
433
  end
399
434
 
400
435
  def supports_pgcrypto_uuid?
401
- database_version >= 90400 # >= 9.4
436
+ database_version >= 9_04_00 # >= 9.4
402
437
  end
403
438
 
404
439
  def supports_optimizer_hints?
@@ -430,14 +465,21 @@ module ActiveRecord
430
465
  query_value("SELECT pg_advisory_unlock(#{lock_id})")
431
466
  end
432
467
 
433
- def enable_extension(name)
434
- exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\"").tap {
435
- reload_type_map
436
- }
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 }
437
474
  end
438
475
 
439
- def disable_extension(name)
440
- 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 {
441
483
  reload_type_map
442
484
  }
443
485
  end
@@ -451,7 +493,7 @@ module ActiveRecord
451
493
  end
452
494
 
453
495
  def extensions
454
- 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
455
497
  end
456
498
 
457
499
  # Returns a list of defined enum types, and their values.
@@ -459,31 +501,97 @@ module ActiveRecord
459
501
  query = <<~SQL
460
502
  SELECT
461
503
  type.typname AS name,
504
+ type.OID AS oid,
505
+ n.nspname AS schema,
462
506
  string_agg(enum.enumlabel, ',' ORDER BY enum.enumsortorder) AS value
463
507
  FROM pg_enum AS enum
464
- JOIN pg_type AS type
465
- ON (type.oid = enum.enumtypid)
466
- GROUP BY type.typname;
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;
467
512
  SQL
468
- exec_query(query, "SCHEMA").cast_values
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
469
520
  end
470
521
 
471
522
  # 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(", ")
523
+ def create_enum(name, values, **options)
524
+ sql_values = values.map { |s| quote(s) }.join(", ")
525
+ scope = quoted_scope(name)
474
526
  query = <<~SQL
475
527
  DO $$
476
528
  BEGIN
477
529
  IF NOT EXISTS (
478
- SELECT 1 FROM pg_type t
479
- WHERE t.typname = '#{name}'
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]}
480
535
  ) THEN
481
- CREATE TYPE \"#{name}\" AS ENUM (#{sql_values});
536
+ CREATE TYPE #{quote_table_name(name)} AS ENUM (#{sql_values});
482
537
  END IF;
483
538
  END
484
539
  $$;
485
540
  SQL
486
- exec_query(query)
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
+ }
487
595
  end
488
596
 
489
597
  # Returns the configured supported identifier length supported by PostgreSQL
@@ -494,7 +602,7 @@ module ActiveRecord
494
602
  # Set the authorized user for this session
495
603
  def session_auth=(user)
496
604
  clear_cache!
497
- execute("SET SESSION AUTHORIZATION #{user}")
605
+ internal_execute("SET SESSION AUTHORIZATION #{user}", nil, materialize_transactions: true)
498
606
  end
499
607
 
500
608
  def use_insert_returning?
@@ -503,7 +611,9 @@ module ActiveRecord
503
611
 
504
612
  # Returns the version of the connected PostgreSQL server.
505
613
  def get_database_version # :nodoc:
506
- @connection.server_version
614
+ with_raw_connection do |conn|
615
+ conn.server_version
616
+ end
507
617
  end
508
618
  alias :postgresql_version :database_version
509
619
 
@@ -531,7 +641,7 @@ module ActiveRecord
531
641
  end
532
642
 
533
643
  def check_version # :nodoc:
534
- if database_version < 90300 # < 9.3
644
+ if database_version < 9_03_00 # < 9.3
535
645
  raise "Your version of PostgreSQL (#{database_version}) is too old. Active Record supports PostgreSQL >= 9.3."
536
646
  end
537
647
  end
@@ -542,8 +652,8 @@ module ActiveRecord
542
652
  m.register_type "int4", Type::Integer.new(limit: 4)
543
653
  m.register_type "int8", Type::Integer.new(limit: 8)
544
654
  m.register_type "oid", OID::Oid.new
545
- m.register_type "float4", Type::Float.new
546
- m.alias_type "float8", "float4"
655
+ m.register_type "float4", Type::Float.new(limit: 24)
656
+ m.register_type "float8", Type::Float.new
547
657
  m.register_type "text", Type::Text.new
548
658
  register_class_with_limit m, "varchar", Type::String
549
659
  m.alias_type "char", "varchar"
@@ -575,10 +685,6 @@ module ActiveRecord
575
685
  m.register_type "polygon", OID::SpecializedString.new(:polygon)
576
686
  m.register_type "circle", OID::SpecializedString.new(:circle)
577
687
 
578
- register_class_with_precision m, "time", Type::Time
579
- register_class_with_precision m, "timestamp", OID::Timestamp
580
- register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
581
-
582
688
  m.register_type "numeric" do |_, fmod, sql_type|
583
689
  precision = extract_precision(sql_type)
584
690
  scale = extract_scale(sql_type)
@@ -607,12 +713,15 @@ module ActiveRecord
607
713
  end
608
714
 
609
715
  private
610
- def type_map
611
- @type_map ||= Type::HashLookupTypeMap.new
612
- end
716
+ attr_reader :type_map
613
717
 
614
718
  def initialize_type_map(m = type_map)
615
719
  self.class.initialize_type_map(m)
720
+
721
+ self.class.register_class_with_precision m, "time", Type::Time, timezone: @default_timezone
722
+ self.class.register_class_with_precision m, "timestamp", OID::Timestamp, timezone: @default_timezone
723
+ self.class.register_class_with_precision m, "timestamptz", OID::TimestampWithTimeZone
724
+
616
725
  load_additional_types
617
726
  end
618
727
 
@@ -668,36 +777,54 @@ module ActiveRecord
668
777
 
669
778
  case exception.result.try(:error_field, PG::PG_DIAG_SQLSTATE)
670
779
  when nil
671
- if exception.message.match?(/connection is closed/i)
672
- ConnectionNotEstablished.new(exception)
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
673
793
  else
674
794
  super
675
795
  end
676
796
  when UNIQUE_VIOLATION
677
- RecordNotUnique.new(message, sql: sql, binds: binds)
797
+ RecordNotUnique.new(message, sql: sql, binds: binds, connection_pool: @pool)
678
798
  when FOREIGN_KEY_VIOLATION
679
- InvalidForeignKey.new(message, sql: sql, binds: binds)
799
+ InvalidForeignKey.new(message, sql: sql, binds: binds, connection_pool: @pool)
680
800
  when VALUE_LIMIT_VIOLATION
681
- ValueTooLong.new(message, sql: sql, binds: binds)
801
+ ValueTooLong.new(message, sql: sql, binds: binds, connection_pool: @pool)
682
802
  when NUMERIC_VALUE_OUT_OF_RANGE
683
- RangeError.new(message, sql: sql, binds: binds)
803
+ RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
684
804
  when NOT_NULL_VIOLATION
685
- NotNullViolation.new(message, sql: sql, binds: binds)
805
+ NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
686
806
  when SERIALIZATION_FAILURE
687
- SerializationFailure.new(message, sql: sql, binds: binds)
807
+ SerializationFailure.new(message, sql: sql, binds: binds, connection_pool: @pool)
688
808
  when DEADLOCK_DETECTED
689
- Deadlocked.new(message, sql: sql, binds: binds)
809
+ Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
690
810
  when DUPLICATE_DATABASE
691
- DatabaseAlreadyExists.new(message, sql: sql, binds: binds)
811
+ DatabaseAlreadyExists.new(message, sql: sql, binds: binds, connection_pool: @pool)
692
812
  when LOCK_NOT_AVAILABLE
693
- LockWaitTimeout.new(message, sql: sql, binds: binds)
813
+ LockWaitTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
694
814
  when QUERY_CANCELED
695
- QueryCanceled.new(message, sql: sql, binds: binds)
815
+ QueryCanceled.new(message, sql: sql, binds: binds, connection_pool: @pool)
696
816
  else
697
817
  super
698
818
  end
699
819
  end
700
820
 
821
+ def retryable_query_error?(exception)
822
+ # We cannot retry anything if we're inside a broken transaction; we need to at
823
+ # least raise until the innermost savepoint is rolled back
824
+ @raw_connection&.transaction_status != ::PG::PQTRANS_INERROR &&
825
+ super
826
+ end
827
+
701
828
  def get_oid_type(oid, fmod, column_name, sql_type = "")
702
829
  if !type_map.key?(oid)
703
830
  load_additional_types([oid])
@@ -714,7 +841,7 @@ module ActiveRecord
714
841
  def load_additional_types(oids = nil)
715
842
  initializer = OID::TypeMapInitializer.new(type_map)
716
843
  load_types_queries(initializer, oids) do |query|
717
- execute_and_clear(query, "SCHEMA", []) do |records|
844
+ execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |records|
718
845
  initializer.run(records)
719
846
  end
720
847
  end
@@ -737,14 +864,14 @@ module ActiveRecord
737
864
 
738
865
  FEATURE_NOT_SUPPORTED = "0A000" # :nodoc:
739
866
 
740
- def execute_and_clear(sql, name, binds, prepare: false, async: false)
867
+ def execute_and_clear(sql, name, binds, prepare: false, async: false, allow_retry: false, materialize_transactions: true)
741
868
  sql = transform_query(sql)
742
869
  check_if_write_query(sql)
743
870
 
744
871
  if !prepare || without_prepared_statement?(binds)
745
- result = exec_no_cache(sql, name, binds, async: async)
872
+ result = exec_no_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
746
873
  else
747
- result = exec_cache(sql, name, binds, async: async)
874
+ result = exec_cache(sql, name, binds, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions)
748
875
  end
749
876
  begin
750
877
  ret = yield result
@@ -754,8 +881,7 @@ module ActiveRecord
754
881
  ret
755
882
  end
756
883
 
757
- def exec_no_cache(sql, name, binds, async: false)
758
- materialize_transactions
884
+ def exec_no_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
759
885
  mark_transaction_written_if_write(sql)
760
886
 
761
887
  # make sure we carry over any changes to ActiveRecord.default_timezone that have been
@@ -763,24 +889,30 @@ module ActiveRecord
763
889
  update_typemap_for_default_timezone
764
890
 
765
891
  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)
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
769
898
  end
770
899
  end
771
900
  end
772
901
 
773
- def exec_cache(sql, name, binds, async: false)
774
- materialize_transactions
902
+ def exec_cache(sql, name, binds, async:, allow_retry:, materialize_transactions:)
775
903
  mark_transaction_written_if_write(sql)
904
+
776
905
  update_typemap_for_default_timezone
777
906
 
778
- stmt_key = prepare_statement(sql, binds)
779
- 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)
780
910
 
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)
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
784
916
  end
785
917
  end
786
918
  rescue ActiveRecord::StatementInvalid => e
@@ -789,7 +921,7 @@ module ActiveRecord
789
921
  # Nothing we can do if we are in a transaction because all commands
790
922
  # will raise InFailedSQLTransaction
791
923
  if in_transaction?
792
- raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
924
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message, connection_pool: @pool)
793
925
  else
794
926
  @lock.synchronize do
795
927
  # outside of transactions we can simply flush this query and retry
@@ -828,70 +960,100 @@ module ActiveRecord
828
960
 
829
961
  # Prepare the statement if it hasn't been prepared, return
830
962
  # 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
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)
844
971
  end
845
- @statements[sql_key]
972
+ # Clear the queue
973
+ conn.get_last_result
974
+ @statements[sql_key] = nextkey
846
975
  end
976
+ @statements[sql_key]
847
977
  end
848
978
 
849
979
  # Connects to a PostgreSQL server and sets up the adapter depending on the
850
980
  # connected server's characteristics.
851
981
  def connect
852
- @connection = self.class.new_client(@connection_parameters)
853
- configure_connection
854
- add_pg_encoders
855
- 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
856
995
  end
857
996
 
858
997
  # Configures the encoding, verbosity, schema search path, and time zone of the connection.
859
998
  # This is called by #connect and should not be called manually.
860
999
  def configure_connection
1000
+ super
1001
+
861
1002
  if @config[:encoding]
862
- @connection.set_client_encoding(@config[:encoding])
1003
+ @raw_connection.set_client_encoding(@config[:encoding])
863
1004
  end
864
1005
  self.client_min_messages = @config[:min_messages] || "warning"
865
1006
  self.schema_search_path = @config[:schema_search_path] || @config[:schema_order]
866
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
+
867
1017
  # Use standard-conforming strings so we don't have to do the E'...' dance.
868
1018
  set_standard_conforming_strings
869
1019
 
870
1020
  variables = @config.fetch(:variables, {}).stringify_keys
871
1021
 
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
1022
  # Set interval output format to ISO 8601 for ease of parsing by ActiveSupport::Duration.parse
883
- execute("SET intervalstyle = iso_8601", "SCHEMA")
1023
+ internal_execute("SET intervalstyle = iso_8601")
884
1024
 
885
1025
  # SET statements from :variables config hash
886
1026
  # https://www.postgresql.org/docs/current/static/sql-set.html
887
1027
  variables.map do |k, v|
888
1028
  if v == ":default" || v == :default
889
1029
  # Sets the value to the global or compile default
890
- execute("SET SESSION #{k} TO DEFAULT", "SCHEMA")
1030
+ internal_execute("SET SESSION #{k} TO DEFAULT")
891
1031
  elsif !v.nil?
892
- execute("SET SESSION #{k} TO #{quote(v)}", "SCHEMA")
1032
+ internal_execute("SET SESSION #{k} TO #{quote(v)}")
893
1033
  end
894
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
895
1057
  end
896
1058
 
897
1059
  # Returns the list of a table's column names, data types, and default values.
@@ -917,6 +1079,7 @@ module ActiveRecord
917
1079
  SELECT a.attname, format_type(a.atttypid, a.atttypmod),
918
1080
  pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
919
1081
  c.collname, col_description(a.attrelid, a.attnum) AS comment,
1082
+ #{supports_identity_columns? ? 'attidentity' : quote('')} AS identity,
920
1083
  #{supports_virtual_columns? ? 'attgenerated' : quote('')} as attgenerated
921
1084
  FROM pg_attribute a
922
1085
  LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid AND a.attnum = d.adnum
@@ -928,37 +1091,37 @@ module ActiveRecord
928
1091
  SQL
929
1092
  end
930
1093
 
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
1094
  def arel_visitor
937
1095
  Arel::Visitors::PostgreSQL.new(self)
938
1096
  end
939
1097
 
940
1098
  def build_statement_pool
941
- 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]))
942
1100
  end
943
1101
 
944
1102
  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)
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
962
1125
  end
963
1126
  end
964
1127
  end
@@ -968,28 +1131,30 @@ module ActiveRecord
968
1131
  map[Integer] = PG::TextEncoder::Integer.new
969
1132
  map[TrueClass] = PG::TextEncoder::Boolean.new
970
1133
  map[FalseClass] = PG::TextEncoder::Boolean.new
971
- @connection.type_map_for_queries = map
1134
+ @raw_connection.type_map_for_queries = map
972
1135
  end
973
1136
 
974
1137
  def update_typemap_for_default_timezone
975
- if @default_timezone != ActiveRecord.default_timezone && @timestamp_decoder
976
- decoder_class = ActiveRecord.default_timezone == :utc ?
1138
+ if @raw_connection && @mapped_default_timezone != default_timezone && @timestamp_decoder
1139
+ decoder_class = default_timezone == :utc ?
977
1140
  PG::TextDecoder::TimestampUtc :
978
1141
  PG::TextDecoder::TimestampWithoutTimeZone
979
1142
 
980
1143
  @timestamp_decoder = decoder_class.new(**@timestamp_decoder.to_h)
981
- @connection.type_map_for_results.add_coder(@timestamp_decoder)
1144
+ @raw_connection.type_map_for_results.add_coder(@timestamp_decoder)
982
1145
 
983
- @default_timezone = ActiveRecord.default_timezone
1146
+ @mapped_default_timezone = default_timezone
984
1147
 
985
1148
  # if default timezone has changed, we need to reconfigure the connection
986
1149
  # (specifically, the session time zone)
987
- configure_connection
1150
+ reconfigure_connection_timezone
1151
+
1152
+ true
988
1153
  end
989
1154
  end
990
1155
 
991
1156
  def add_pg_decoders
992
- @default_timezone = nil
1157
+ @mapped_default_timezone = nil
993
1158
  @timestamp_decoder = nil
994
1159
 
995
1160
  coders_by_name = {
@@ -1004,6 +1169,7 @@ module ActiveRecord
1004
1169
  "timestamp" => PG::TextDecoder::TimestampUtc,
1005
1170
  "timestamptz" => PG::TextDecoder::TimestampWithTimeZone,
1006
1171
  }
1172
+ coders_by_name["date"] = PG::TextDecoder::Date if decode_dates
1007
1173
 
1008
1174
  known_coder_types = coders_by_name.keys.map { |n| quote(n) }
1009
1175
  query = <<~SQL % known_coder_types.join(", ")
@@ -1011,13 +1177,13 @@ module ActiveRecord
1011
1177
  FROM pg_type as t
1012
1178
  WHERE t.typname IN (%s)
1013
1179
  SQL
1014
- coders = execute_and_clear(query, "SCHEMA", []) do |result|
1180
+ coders = execute_and_clear(query, "SCHEMA", [], allow_retry: true, materialize_transactions: false) do |result|
1015
1181
  result.filter_map { |row| construct_coder(row, coders_by_name[row["typname"]]) }
1016
1182
  end
1017
1183
 
1018
1184
  map = PG::TypeMapByOid.new
1019
1185
  coders.each { |coder| map.add_coder(coder) }
1020
- @connection.type_map_for_results = map
1186
+ @raw_connection.type_map_for_results = map
1021
1187
 
1022
1188
  @type_map_for_results = PG::TypeMapByOid.new
1023
1189
  @type_map_for_results.default_type_map = map