activerecord 6.1.7 → 7.1.0

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