activerecord 6.1.6 → 7.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (309) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1627 -983
  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 +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 +439 -305
  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 +73 -22
  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 -34
  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 +172 -50
  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 -138
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -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 +151 -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 +30 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -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/money.rb +3 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  95. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  97. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +394 -74
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
  101. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  102. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  107. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  108. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  109. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  110. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  111. data/lib/active_record/connection_adapters.rb +9 -6
  112. data/lib/active_record/connection_handling.rb +107 -136
  113. data/lib/active_record/core.rb +202 -223
  114. data/lib/active_record/counter_cache.rb +46 -25
  115. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  116. data/lib/active_record/database_configurations/database_config.rb +21 -12
  117. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  118. data/lib/active_record/database_configurations/url_config.rb +18 -12
  119. data/lib/active_record/database_configurations.rb +95 -59
  120. data/lib/active_record/delegated_type.rb +61 -15
  121. data/lib/active_record/deprecator.rb +7 -0
  122. data/lib/active_record/destroy_association_async_job.rb +3 -1
  123. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  124. data/lib/active_record/dynamic_matchers.rb +1 -1
  125. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  126. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  127. data/lib/active_record/encryption/cipher.rb +53 -0
  128. data/lib/active_record/encryption/config.rb +68 -0
  129. data/lib/active_record/encryption/configurable.rb +60 -0
  130. data/lib/active_record/encryption/context.rb +42 -0
  131. data/lib/active_record/encryption/contexts.rb +76 -0
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  134. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  136. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  137. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  138. data/lib/active_record/encryption/encryptor.rb +155 -0
  139. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  140. data/lib/active_record/encryption/errors.rb +15 -0
  141. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  142. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  143. data/lib/active_record/encryption/key.rb +28 -0
  144. data/lib/active_record/encryption/key_generator.rb +53 -0
  145. data/lib/active_record/encryption/key_provider.rb +46 -0
  146. data/lib/active_record/encryption/message.rb +33 -0
  147. data/lib/active_record/encryption/message_serializer.rb +92 -0
  148. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  149. data/lib/active_record/encryption/properties.rb +76 -0
  150. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  151. data/lib/active_record/encryption/scheme.rb +96 -0
  152. data/lib/active_record/encryption.rb +56 -0
  153. data/lib/active_record/enum.rb +154 -63
  154. data/lib/active_record/errors.rb +171 -15
  155. data/lib/active_record/explain.rb +23 -3
  156. data/lib/active_record/explain_registry.rb +11 -6
  157. data/lib/active_record/explain_subscriber.rb +1 -1
  158. data/lib/active_record/fixture_set/file.rb +15 -1
  159. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  160. data/lib/active_record/fixture_set/render_context.rb +2 -0
  161. data/lib/active_record/fixture_set/table_row.rb +70 -14
  162. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  163. data/lib/active_record/fixtures.rb +131 -86
  164. data/lib/active_record/future_result.rb +164 -0
  165. data/lib/active_record/gem_version.rb +3 -3
  166. data/lib/active_record/inheritance.rb +81 -29
  167. data/lib/active_record/insert_all.rb +135 -22
  168. data/lib/active_record/integration.rb +11 -10
  169. data/lib/active_record/internal_metadata.rb +119 -33
  170. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  171. data/lib/active_record/locking/optimistic.rb +36 -21
  172. data/lib/active_record/locking/pessimistic.rb +15 -6
  173. data/lib/active_record/log_subscriber.rb +52 -19
  174. data/lib/active_record/marshalling.rb +56 -0
  175. data/lib/active_record/message_pack.rb +124 -0
  176. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  177. data/lib/active_record/middleware/database_selector.rb +23 -13
  178. data/lib/active_record/middleware/shard_selector.rb +62 -0
  179. data/lib/active_record/migration/command_recorder.rb +112 -14
  180. data/lib/active_record/migration/compatibility.rb +221 -48
  181. data/lib/active_record/migration/default_strategy.rb +23 -0
  182. data/lib/active_record/migration/execution_strategy.rb +19 -0
  183. data/lib/active_record/migration/join_table.rb +1 -1
  184. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  185. data/lib/active_record/migration.rb +358 -171
  186. data/lib/active_record/model_schema.rb +120 -101
  187. data/lib/active_record/nested_attributes.rb +37 -18
  188. data/lib/active_record/no_touching.rb +3 -3
  189. data/lib/active_record/normalization.rb +167 -0
  190. data/lib/active_record/persistence.rb +405 -85
  191. data/lib/active_record/promise.rb +84 -0
  192. data/lib/active_record/query_cache.rb +3 -21
  193. data/lib/active_record/query_logs.rb +174 -0
  194. data/lib/active_record/query_logs_formatter.rb +41 -0
  195. data/lib/active_record/querying.rb +29 -6
  196. data/lib/active_record/railtie.rb +219 -43
  197. data/lib/active_record/railties/controller_runtime.rb +13 -9
  198. data/lib/active_record/railties/databases.rake +188 -252
  199. data/lib/active_record/railties/job_runtime.rb +23 -0
  200. data/lib/active_record/readonly_attributes.rb +41 -3
  201. data/lib/active_record/reflection.rb +241 -80
  202. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  203. data/lib/active_record/relation/batches.rb +192 -63
  204. data/lib/active_record/relation/calculations.rb +219 -90
  205. data/lib/active_record/relation/delegation.rb +27 -13
  206. data/lib/active_record/relation/finder_methods.rb +108 -51
  207. data/lib/active_record/relation/merger.rb +22 -13
  208. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  209. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  210. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  211. data/lib/active_record/relation/predicate_builder.rb +27 -20
  212. data/lib/active_record/relation/query_attribute.rb +30 -12
  213. data/lib/active_record/relation/query_methods.rb +654 -127
  214. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  215. data/lib/active_record/relation/spawn_methods.rb +20 -3
  216. data/lib/active_record/relation/where_clause.rb +10 -19
  217. data/lib/active_record/relation.rb +262 -120
  218. data/lib/active_record/result.rb +37 -11
  219. data/lib/active_record/runtime_registry.rb +18 -13
  220. data/lib/active_record/sanitization.rb +65 -20
  221. data/lib/active_record/schema.rb +36 -22
  222. data/lib/active_record/schema_dumper.rb +73 -24
  223. data/lib/active_record/schema_migration.rb +68 -33
  224. data/lib/active_record/scoping/default.rb +72 -15
  225. data/lib/active_record/scoping/named.rb +5 -13
  226. data/lib/active_record/scoping.rb +65 -34
  227. data/lib/active_record/secure_password.rb +60 -0
  228. data/lib/active_record/secure_token.rb +21 -3
  229. data/lib/active_record/serialization.rb +6 -1
  230. data/lib/active_record/signed_id.rb +10 -8
  231. data/lib/active_record/store.rb +16 -11
  232. data/lib/active_record/suppressor.rb +13 -15
  233. data/lib/active_record/table_metadata.rb +16 -3
  234. data/lib/active_record/tasks/database_tasks.rb +225 -136
  235. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  236. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  237. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  238. data/lib/active_record/test_databases.rb +1 -1
  239. data/lib/active_record/test_fixtures.rb +123 -99
  240. data/lib/active_record/timestamp.rb +29 -18
  241. data/lib/active_record/token_for.rb +113 -0
  242. data/lib/active_record/touch_later.rb +11 -6
  243. data/lib/active_record/transactions.rb +48 -27
  244. data/lib/active_record/translation.rb +3 -3
  245. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  246. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  247. data/lib/active_record/type/internal/timezone.rb +7 -2
  248. data/lib/active_record/type/serialized.rb +9 -5
  249. data/lib/active_record/type/time.rb +4 -0
  250. data/lib/active_record/type/type_map.rb +17 -20
  251. data/lib/active_record/type.rb +1 -2
  252. data/lib/active_record/validations/absence.rb +1 -1
  253. data/lib/active_record/validations/associated.rb +4 -4
  254. data/lib/active_record/validations/numericality.rb +5 -4
  255. data/lib/active_record/validations/presence.rb +5 -28
  256. data/lib/active_record/validations/uniqueness.rb +51 -6
  257. data/lib/active_record/validations.rb +8 -4
  258. data/lib/active_record/version.rb +1 -1
  259. data/lib/active_record.rb +335 -32
  260. data/lib/arel/attributes/attribute.rb +0 -8
  261. data/lib/arel/crud.rb +28 -22
  262. data/lib/arel/delete_manager.rb +18 -4
  263. data/lib/arel/errors.rb +10 -0
  264. data/lib/arel/factory_methods.rb +4 -0
  265. data/lib/arel/filter_predications.rb +9 -0
  266. data/lib/arel/insert_manager.rb +2 -3
  267. data/lib/arel/nodes/and.rb +4 -0
  268. data/lib/arel/nodes/binary.rb +6 -1
  269. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  270. data/lib/arel/nodes/casted.rb +1 -1
  271. data/lib/arel/nodes/cte.rb +36 -0
  272. data/lib/arel/nodes/delete_statement.rb +12 -13
  273. data/lib/arel/nodes/filter.rb +10 -0
  274. data/lib/arel/nodes/fragments.rb +35 -0
  275. data/lib/arel/nodes/function.rb +1 -0
  276. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  277. data/lib/arel/nodes/insert_statement.rb +2 -2
  278. data/lib/arel/nodes/leading_join.rb +8 -0
  279. data/lib/arel/nodes/node.rb +111 -2
  280. data/lib/arel/nodes/select_core.rb +2 -2
  281. data/lib/arel/nodes/select_statement.rb +2 -2
  282. data/lib/arel/nodes/sql_literal.rb +6 -0
  283. data/lib/arel/nodes/table_alias.rb +4 -0
  284. data/lib/arel/nodes/update_statement.rb +8 -3
  285. data/lib/arel/nodes.rb +5 -0
  286. data/lib/arel/predications.rb +13 -3
  287. data/lib/arel/select_manager.rb +10 -4
  288. data/lib/arel/table.rb +9 -6
  289. data/lib/arel/tree_manager.rb +0 -12
  290. data/lib/arel/update_manager.rb +18 -4
  291. data/lib/arel/visitors/dot.rb +80 -90
  292. data/lib/arel/visitors/mysql.rb +16 -3
  293. data/lib/arel/visitors/postgresql.rb +0 -10
  294. data/lib/arel/visitors/to_sql.rb +139 -19
  295. data/lib/arel/visitors/visitor.rb +2 -2
  296. data/lib/arel.rb +18 -3
  297. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  298. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  299. data/lib/rails/generators/active_record/migration.rb +3 -1
  300. data/lib/rails/generators/active_record/model/USAGE +113 -0
  301. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  302. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  303. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  304. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  305. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  306. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  307. metadata +93 -13
  308. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  309. 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
@@ -75,14 +66,39 @@ module ActiveRecord
75
66
 
76
67
  class << self
77
68
  def new_client(conn_params)
78
- PG.connect(conn_params)
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