activerecord 6.1.7 → 7.1.5

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