activerecord 6.1.7 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +520 -1385
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +17 -14
  7. data/lib/active_record/association_relation.rb +2 -12
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +60 -21
  10. data/lib/active_record/associations/association_scope.rb +17 -12
  11. data/lib/active_record/associations/belongs_to_association.rb +37 -11
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +13 -4
  13. data/lib/active_record/associations/builder/association.rb +11 -5
  14. data/lib/active_record/associations/builder/belongs_to.rb +41 -14
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +4 -4
  19. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  20. data/lib/active_record/associations/collection_association.rb +46 -36
  21. data/lib/active_record/associations/collection_proxy.rb +44 -16
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/errors.rb +265 -0
  24. data/lib/active_record/associations/foreign_association.rb +10 -3
  25. data/lib/active_record/associations/has_many_association.rb +29 -19
  26. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  27. data/lib/active_record/associations/has_one_association.rb +20 -10
  28. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  29. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  30. data/lib/active_record/associations/join_dependency.rb +23 -15
  31. data/lib/active_record/associations/nested_error.rb +47 -0
  32. data/lib/active_record/associations/preloader/association.rb +212 -53
  33. data/lib/active_record/associations/preloader/batch.rb +48 -0
  34. data/lib/active_record/associations/preloader/branch.rb +153 -0
  35. data/lib/active_record/associations/preloader/through_association.rb +50 -16
  36. data/lib/active_record/associations/preloader.rb +50 -121
  37. data/lib/active_record/associations/singular_association.rb +15 -3
  38. data/lib/active_record/associations/through_association.rb +25 -14
  39. data/lib/active_record/associations.rb +404 -509
  40. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  41. data/lib/active_record/attribute_assignment.rb +2 -14
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  43. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  44. data/lib/active_record/attribute_methods/dirty.rb +73 -22
  45. data/lib/active_record/attribute_methods/primary_key.rb +47 -27
  46. data/lib/active_record/attribute_methods/query.rb +31 -19
  47. data/lib/active_record/attribute_methods/read.rb +14 -11
  48. data/lib/active_record/attribute_methods/serialization.rb +174 -37
  49. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -9
  50. data/lib/active_record/attribute_methods/write.rb +12 -15
  51. data/lib/active_record/attribute_methods.rb +164 -52
  52. data/lib/active_record/attributes.rb +51 -49
  53. data/lib/active_record/autosave_association.rb +74 -57
  54. data/lib/active_record/base.rb +27 -5
  55. data/lib/active_record/callbacks.rb +18 -34
  56. data/lib/active_record/coders/column_serializer.rb +61 -0
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +70 -46
  59. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +284 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  61. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +79 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +327 -612
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -60
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +201 -64
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +119 -131
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +377 -142
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +361 -76
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +624 -163
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +345 -166
  75. data/lib/active_record/connection_adapters/column.rb +13 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -130
  78. data/lib/active_record/connection_adapters/mysql/quoting.rb +81 -55
  79. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  81. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  82. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  83. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +107 -68
  85. data/lib/active_record/connection_adapters/pool_config.rb +26 -16
  86. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  87. data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
  88. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +114 -54
  89. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  94. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  97. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  100. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  101. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  102. data/lib/active_record/connection_adapters/postgresql/quoting.rb +137 -104
  103. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  104. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  105. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +173 -3
  106. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  107. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +401 -77
  108. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  109. data/lib/active_record/connection_adapters/postgresql_adapter.rb +518 -251
  110. data/lib/active_record/connection_adapters/schema_cache.rb +326 -102
  111. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  112. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +78 -55
  113. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +68 -54
  114. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  116. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +66 -22
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +372 -130
  119. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  120. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  121. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  122. data/lib/active_record/connection_adapters.rb +130 -6
  123. data/lib/active_record/connection_handling.rb +132 -146
  124. data/lib/active_record/core.rb +276 -251
  125. data/lib/active_record/counter_cache.rb +68 -34
  126. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -3
  127. data/lib/active_record/database_configurations/database_config.rb +34 -10
  128. data/lib/active_record/database_configurations/hash_config.rb +107 -31
  129. data/lib/active_record/database_configurations/url_config.rb +38 -13
  130. data/lib/active_record/database_configurations.rb +96 -60
  131. data/lib/active_record/delegated_type.rb +90 -20
  132. data/lib/active_record/deprecator.rb +7 -0
  133. data/lib/active_record/destroy_association_async_job.rb +4 -2
  134. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  135. data/lib/active_record/dynamic_matchers.rb +3 -3
  136. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +68 -0
  140. data/lib/active_record/encryption/configurable.rb +60 -0
  141. data/lib/active_record/encryption/context.rb +42 -0
  142. data/lib/active_record/encryption/contexts.rb +76 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +230 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +175 -0
  147. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  148. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  149. data/lib/active_record/encryption/encryptor.rb +170 -0
  150. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  151. data/lib/active_record/encryption/errors.rb +15 -0
  152. data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
  153. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  154. data/lib/active_record/encryption/key.rb +28 -0
  155. data/lib/active_record/encryption/key_generator.rb +53 -0
  156. data/lib/active_record/encryption/key_provider.rb +46 -0
  157. data/lib/active_record/encryption/message.rb +33 -0
  158. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  159. data/lib/active_record/encryption/message_serializer.rb +96 -0
  160. data/lib/active_record/encryption/null_encryptor.rb +25 -0
  161. data/lib/active_record/encryption/properties.rb +76 -0
  162. data/lib/active_record/encryption/read_only_null_encryptor.rb +28 -0
  163. data/lib/active_record/encryption/scheme.rb +100 -0
  164. data/lib/active_record/encryption.rb +56 -0
  165. data/lib/active_record/enum.rb +163 -63
  166. data/lib/active_record/errors.rb +210 -27
  167. data/lib/active_record/explain.rb +21 -12
  168. data/lib/active_record/explain_registry.rb +11 -6
  169. data/lib/active_record/explain_subscriber.rb +1 -1
  170. data/lib/active_record/fixture_set/file.rb +15 -1
  171. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  172. data/lib/active_record/fixture_set/render_context.rb +2 -0
  173. data/lib/active_record/fixture_set/table_row.rb +70 -14
  174. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  175. data/lib/active_record/fixtures.rb +179 -112
  176. data/lib/active_record/future_result.rb +178 -0
  177. data/lib/active_record/gem_version.rb +4 -4
  178. data/lib/active_record/inheritance.rb +85 -31
  179. data/lib/active_record/insert_all.rb +148 -32
  180. data/lib/active_record/integration.rb +14 -10
  181. data/lib/active_record/internal_metadata.rb +123 -23
  182. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  183. data/lib/active_record/locking/optimistic.rb +43 -27
  184. data/lib/active_record/locking/pessimistic.rb +15 -6
  185. data/lib/active_record/log_subscriber.rb +41 -29
  186. data/lib/active_record/marshalling.rb +56 -0
  187. data/lib/active_record/message_pack.rb +124 -0
  188. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  189. data/lib/active_record/middleware/database_selector.rb +23 -13
  190. data/lib/active_record/middleware/shard_selector.rb +62 -0
  191. data/lib/active_record/migration/command_recorder.rb +113 -16
  192. data/lib/active_record/migration/compatibility.rb +235 -46
  193. data/lib/active_record/migration/default_strategy.rb +22 -0
  194. data/lib/active_record/migration/execution_strategy.rb +19 -0
  195. data/lib/active_record/migration/join_table.rb +1 -1
  196. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  197. data/lib/active_record/migration.rb +374 -177
  198. data/lib/active_record/model_schema.rb +143 -159
  199. data/lib/active_record/nested_attributes.rb +48 -21
  200. data/lib/active_record/no_touching.rb +3 -3
  201. data/lib/active_record/normalization.rb +163 -0
  202. data/lib/active_record/persistence.rb +282 -283
  203. data/lib/active_record/promise.rb +84 -0
  204. data/lib/active_record/query_cache.rb +19 -25
  205. data/lib/active_record/query_logs.rb +189 -0
  206. data/lib/active_record/query_logs_formatter.rb +41 -0
  207. data/lib/active_record/querying.rb +44 -9
  208. data/lib/active_record/railtie.rb +234 -71
  209. data/lib/active_record/railties/controller_runtime.rb +25 -11
  210. data/lib/active_record/railties/databases.rake +189 -256
  211. data/lib/active_record/railties/job_runtime.rb +23 -0
  212. data/lib/active_record/readonly_attributes.rb +41 -3
  213. data/lib/active_record/reflection.rb +325 -103
  214. data/lib/active_record/relation/batches/batch_enumerator.rb +38 -9
  215. data/lib/active_record/relation/batches.rb +198 -63
  216. data/lib/active_record/relation/calculations.rb +300 -111
  217. data/lib/active_record/relation/delegation.rb +33 -22
  218. data/lib/active_record/relation/finder_methods.rb +123 -52
  219. data/lib/active_record/relation/merger.rb +26 -19
  220. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  221. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  222. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  223. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  224. data/lib/active_record/relation/predicate_builder.rb +29 -22
  225. data/lib/active_record/relation/query_attribute.rb +30 -12
  226. data/lib/active_record/relation/query_methods.rb +842 -150
  227. data/lib/active_record/relation/record_fetch_warning.rb +10 -9
  228. data/lib/active_record/relation/spawn_methods.rb +7 -6
  229. data/lib/active_record/relation/where_clause.rb +15 -36
  230. data/lib/active_record/relation.rb +736 -145
  231. data/lib/active_record/result.rb +67 -54
  232. data/lib/active_record/runtime_registry.rb +71 -13
  233. data/lib/active_record/sanitization.rb +84 -34
  234. data/lib/active_record/schema.rb +39 -23
  235. data/lib/active_record/schema_dumper.rb +90 -31
  236. data/lib/active_record/schema_migration.rb +74 -23
  237. data/lib/active_record/scoping/default.rb +72 -15
  238. data/lib/active_record/scoping/named.rb +5 -13
  239. data/lib/active_record/scoping.rb +65 -34
  240. data/lib/active_record/secure_password.rb +60 -0
  241. data/lib/active_record/secure_token.rb +21 -3
  242. data/lib/active_record/serialization.rb +6 -1
  243. data/lib/active_record/signed_id.rb +30 -9
  244. data/lib/active_record/statement_cache.rb +7 -7
  245. data/lib/active_record/store.rb +10 -10
  246. data/lib/active_record/suppressor.rb +13 -15
  247. data/lib/active_record/table_metadata.rb +7 -3
  248. data/lib/active_record/tasks/database_tasks.rb +277 -149
  249. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  250. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  251. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  252. data/lib/active_record/test_databases.rb +1 -1
  253. data/lib/active_record/test_fixtures.rb +173 -155
  254. data/lib/active_record/testing/query_assertions.rb +121 -0
  255. data/lib/active_record/timestamp.rb +32 -19
  256. data/lib/active_record/token_for.rb +123 -0
  257. data/lib/active_record/touch_later.rb +12 -7
  258. data/lib/active_record/transaction.rb +132 -0
  259. data/lib/active_record/transactions.rb +118 -41
  260. data/lib/active_record/translation.rb +3 -5
  261. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  262. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  263. data/lib/active_record/type/internal/timezone.rb +7 -2
  264. data/lib/active_record/type/serialized.rb +9 -7
  265. data/lib/active_record/type/time.rb +4 -0
  266. data/lib/active_record/type/type_map.rb +17 -20
  267. data/lib/active_record/type.rb +1 -2
  268. data/lib/active_record/type_caster/connection.rb +4 -4
  269. data/lib/active_record/validations/absence.rb +1 -1
  270. data/lib/active_record/validations/associated.rb +13 -7
  271. data/lib/active_record/validations/numericality.rb +5 -4
  272. data/lib/active_record/validations/presence.rb +5 -28
  273. data/lib/active_record/validations/uniqueness.rb +64 -15
  274. data/lib/active_record/validations.rb +12 -5
  275. data/lib/active_record/version.rb +1 -1
  276. data/lib/active_record.rb +444 -32
  277. data/lib/arel/alias_predication.rb +1 -1
  278. data/lib/arel/attributes/attribute.rb +0 -8
  279. data/lib/arel/collectors/bind.rb +2 -0
  280. data/lib/arel/collectors/composite.rb +7 -0
  281. data/lib/arel/collectors/sql_string.rb +1 -1
  282. data/lib/arel/collectors/substitute_binds.rb +1 -1
  283. data/lib/arel/crud.rb +28 -22
  284. data/lib/arel/delete_manager.rb +18 -4
  285. data/lib/arel/errors.rb +10 -0
  286. data/lib/arel/factory_methods.rb +4 -0
  287. data/lib/arel/filter_predications.rb +9 -0
  288. data/lib/arel/insert_manager.rb +2 -3
  289. data/lib/arel/nodes/binary.rb +6 -7
  290. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  291. data/lib/arel/nodes/casted.rb +1 -1
  292. data/lib/arel/nodes/cte.rb +36 -0
  293. data/lib/arel/nodes/delete_statement.rb +12 -13
  294. data/lib/arel/nodes/filter.rb +10 -0
  295. data/lib/arel/nodes/fragments.rb +35 -0
  296. data/lib/arel/nodes/function.rb +1 -0
  297. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  298. data/lib/arel/nodes/insert_statement.rb +2 -2
  299. data/lib/arel/nodes/leading_join.rb +8 -0
  300. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  301. data/lib/arel/nodes/node.rb +115 -5
  302. data/lib/arel/nodes/select_core.rb +2 -2
  303. data/lib/arel/nodes/select_statement.rb +2 -2
  304. data/lib/arel/nodes/sql_literal.rb +13 -0
  305. data/lib/arel/nodes/table_alias.rb +4 -0
  306. data/lib/arel/nodes/update_statement.rb +8 -3
  307. data/lib/arel/nodes.rb +7 -2
  308. data/lib/arel/predications.rb +14 -4
  309. data/lib/arel/select_manager.rb +11 -5
  310. data/lib/arel/table.rb +9 -6
  311. data/lib/arel/tree_manager.rb +8 -15
  312. data/lib/arel/update_manager.rb +20 -5
  313. data/lib/arel/visitors/dot.rb +81 -90
  314. data/lib/arel/visitors/mysql.rb +23 -5
  315. data/lib/arel/visitors/postgresql.rb +1 -22
  316. data/lib/arel/visitors/to_sql.rb +170 -36
  317. data/lib/arel/visitors/visitor.rb +2 -2
  318. data/lib/arel.rb +23 -4
  319. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  320. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  321. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  322. data/lib/rails/generators/active_record/migration.rb +3 -1
  323. data/lib/rails/generators/active_record/model/USAGE +113 -0
  324. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  325. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  326. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  327. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  328. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  329. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  330. metadata +100 -14
  331. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  332. data/lib/active_record/null_relation.rb +0 -67
@@ -4,6 +4,7 @@ require "set"
4
4
  require "active_record/connection_adapters/sql_type_metadata"
5
5
  require "active_record/connection_adapters/abstract/schema_dumper"
6
6
  require "active_record/connection_adapters/abstract/schema_creation"
7
+ require "active_support/concurrency/null_lock"
7
8
  require "active_support/concurrency/load_interlock_aware_monitor"
8
9
  require "arel/collectors/bind"
9
10
  require "arel/collectors/composite"
@@ -12,6 +13,8 @@ require "arel/collectors/substitute_binds"
12
13
 
13
14
  module ActiveRecord
14
15
  module ConnectionAdapters # :nodoc:
16
+ # = Active Record Abstract Adapter
17
+ #
15
18
  # Active Record supports multiple database systems. AbstractAdapter and
16
19
  # related classes form the abstraction layer which makes this possible.
17
20
  # An AbstractAdapter represents a connection to a database, and provides an
@@ -20,7 +23,7 @@ module ActiveRecord
20
23
  # and +:limit+ options, etc.
21
24
  #
22
25
  # All the concrete database adapters follow the interface laid down in this class.
23
- # {ActiveRecord::Base.connection}[rdoc-ref:ConnectionHandling#connection] returns an AbstractAdapter object, which
26
+ # {ActiveRecord::Base.lease_connection}[rdoc-ref:ConnectionHandling#lease_connection] returns an AbstractAdapter object, which
24
27
  # you can use.
25
28
  #
26
29
  # Most of the methods in the adapter are useful during migrations. Most
@@ -36,12 +39,18 @@ module ActiveRecord
36
39
  include Savepoints
37
40
 
38
41
  SIMPLE_INT = /\A\d+\z/
39
- COMMENT_REGEX = %r{(?:\-\-.*\n)*|/\*(?:[^\*]|\*[^/])*\*/}m
42
+ COMMENT_REGEX = %r{(?:--.*\n)|/\*(?:[^*]|\*[^/])*\*/}
40
43
 
41
- attr_accessor :pool
44
+ attr_reader :pool
42
45
  attr_reader :visitor, :owner, :logger, :lock
43
46
  alias :in_use? :owner
44
47
 
48
+ def pool=(value)
49
+ return if value.eql?(@pool)
50
+ @schema_cache = nil
51
+ @pool = value
52
+ end
53
+
45
54
  set_callback :checkin, :after, :enable_lazy_transactions!
46
55
 
47
56
  def self.type_cast_config_to_integer(config)
@@ -62,95 +71,174 @@ module ActiveRecord
62
71
  end
63
72
  end
64
73
 
74
+ def self.validate_default_timezone(config)
75
+ case config
76
+ when nil
77
+ when "utc", "local"
78
+ config.to_sym
79
+ else
80
+ raise ArgumentError, "default_timezone must be either 'utc' or 'local'"
81
+ end
82
+ end
83
+
65
84
  DEFAULT_READ_QUERY = [:begin, :commit, :explain, :release, :rollback, :savepoint, :select, :with] # :nodoc:
66
85
  private_constant :DEFAULT_READ_QUERY
67
86
 
68
87
  def self.build_read_query_regexp(*parts) # :nodoc:
69
88
  parts += DEFAULT_READ_QUERY
70
89
  parts = parts.map { |part| /#{part}/i }
71
- /\A(?:[\(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
90
+ /\A(?:[(\s]|#{COMMENT_REGEX})*#{Regexp.union(*parts)}/
72
91
  end
73
92
 
74
- def self.quoted_column_names # :nodoc:
75
- @quoted_column_names ||= {}
93
+ def self.find_cmd_and_exec(commands, *args) # :doc:
94
+ commands = Array(commands)
95
+
96
+ dirs_on_path = ENV["PATH"].to_s.split(File::PATH_SEPARATOR)
97
+ unless (ext = RbConfig::CONFIG["EXEEXT"]).empty?
98
+ commands = commands.map { |cmd| "#{cmd}#{ext}" }
99
+ end
100
+
101
+ full_path_command = nil
102
+ found = commands.detect do |cmd|
103
+ dirs_on_path.detect do |path|
104
+ full_path_command = File.join(path, cmd)
105
+ begin
106
+ stat = File.stat(full_path_command)
107
+ rescue SystemCallError
108
+ else
109
+ stat.file? && stat.executable?
110
+ end
111
+ end
112
+ end
113
+
114
+ if found
115
+ exec full_path_command, *args
116
+ else
117
+ abort("Couldn't find database client: #{commands.join(', ')}. Check your $PATH and try again.")
118
+ end
76
119
  end
77
120
 
78
- def self.quoted_table_names # :nodoc:
79
- @quoted_table_names ||= {}
121
+ # Opens a database console session.
122
+ def self.dbconsole(config, options = {})
123
+ raise NotImplementedError
80
124
  end
81
125
 
82
- def initialize(connection, logger = nil, config = {}) # :nodoc:
126
+ def initialize(config_or_deprecated_connection, deprecated_logger = nil, deprecated_connection_options = nil, deprecated_config = nil) # :nodoc:
83
127
  super()
84
128
 
85
- @connection = connection
86
- @owner = nil
87
- @instrumenter = ActiveSupport::Notifications.instrumenter
88
- @logger = logger
89
- @config = config
90
- @pool = ActiveRecord::ConnectionAdapters::NullPool.new
91
- @idle_since = Concurrent.monotonic_time
129
+ @raw_connection = nil
130
+ @unconfigured_connection = nil
131
+
132
+ if config_or_deprecated_connection.is_a?(Hash)
133
+ @config = config_or_deprecated_connection.symbolize_keys
134
+ @logger = ActiveRecord::Base.logger
135
+
136
+ if deprecated_logger || deprecated_connection_options || deprecated_config
137
+ raise ArgumentError, "when initializing an Active Record adapter with a config hash, that should be the only argument"
138
+ end
139
+ else
140
+ # Soft-deprecated for now; we'll probably warn in future.
141
+
142
+ @unconfigured_connection = config_or_deprecated_connection
143
+ @logger = deprecated_logger || ActiveRecord::Base.logger
144
+ if deprecated_config
145
+ @config = (deprecated_config || {}).symbolize_keys
146
+ @connection_parameters = deprecated_connection_options
147
+ else
148
+ @config = (deprecated_connection_options || {}).symbolize_keys
149
+ @connection_parameters = nil
150
+ end
151
+ end
152
+
153
+ @owner = nil
154
+ @instrumenter = ActiveSupport::Notifications.instrumenter
155
+ @pool = ActiveRecord::ConnectionAdapters::NullPool.new
156
+ @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
92
157
  @visitor = arel_visitor
93
158
  @statements = build_statement_pool
94
- @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
159
+ self.lock_thread = nil
95
160
 
96
- @prepared_statements = self.class.type_cast_config_to_boolean(
97
- config.fetch(:prepared_statements, true)
161
+ @prepared_statements = !ActiveRecord.disable_prepared_statements && self.class.type_cast_config_to_boolean(
162
+ @config.fetch(:prepared_statements) { default_prepared_statements }
98
163
  )
99
164
 
100
165
  @advisory_locks_enabled = self.class.type_cast_config_to_boolean(
101
- config.fetch(:advisory_locks, true)
166
+ @config.fetch(:advisory_locks, true)
102
167
  )
103
- end
104
168
 
105
- def replica?
106
- @config[:replica] || false
169
+ @default_timezone = self.class.validate_default_timezone(@config[:default_timezone])
170
+
171
+ @raw_connection_dirty = false
172
+ @verified = false
107
173
  end
108
174
 
109
- def use_metadata_table?
110
- @config.fetch(:use_metadata_table, true)
175
+ def inspect # :nodoc:
176
+ name_field = " name=#{pool.db_config.name.inspect}" unless pool.db_config.name == "primary"
177
+ shard_field = " shard=#{shard.inspect}" unless shard == :default
178
+
179
+ "#<#{self.class.name}:#{'%#016x' % (object_id << 1)} env_name=#{pool.db_config.env_name.inspect}#{name_field} role=#{role.inspect}#{shard_field}>"
111
180
  end
112
181
 
113
- # Determines whether writes are currently being prevented.
114
- #
115
- # Returns true if the connection is a replica.
116
- #
117
- # If the application is using legacy handling, returns
118
- # true if +connection_handler.prevent_writes+ is set.
119
- #
120
- # If the application is using the new connection handling
121
- # will return true based on +current_preventing_writes+.
122
- def preventing_writes?
123
- return true if replica?
124
- return ActiveRecord::Base.connection_handler.prevent_writes if ActiveRecord::Base.legacy_connection_handling
125
- return false if connection_klass.nil?
182
+ def lock_thread=(lock_thread) # :nodoc:
183
+ @lock =
184
+ case lock_thread
185
+ when Thread
186
+ ActiveSupport::Concurrency::ThreadLoadInterlockAwareMonitor.new
187
+ when Fiber
188
+ ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
189
+ else
190
+ ActiveSupport::Concurrency::NullLock
191
+ end
192
+ end
126
193
 
127
- connection_klass.current_preventing_writes
194
+ EXCEPTION_NEVER = { Exception => :never }.freeze # :nodoc:
195
+ EXCEPTION_IMMEDIATE = { Exception => :immediate }.freeze # :nodoc:
196
+ private_constant :EXCEPTION_NEVER, :EXCEPTION_IMMEDIATE
197
+ def with_instrumenter(instrumenter, &block) # :nodoc:
198
+ Thread.handle_interrupt(EXCEPTION_NEVER) do
199
+ previous_instrumenter = @instrumenter
200
+ @instrumenter = instrumenter
201
+ Thread.handle_interrupt(EXCEPTION_IMMEDIATE, &block)
202
+ ensure
203
+ @instrumenter = previous_instrumenter
204
+ end
128
205
  end
129
206
 
130
- def migrations_paths # :nodoc:
131
- @config[:migrations_paths] || Migrator.migrations_paths
207
+ def check_if_write_query(sql) # :nodoc:
208
+ if preventing_writes? && write_query?(sql)
209
+ raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
210
+ end
132
211
  end
133
212
 
134
- def migration_context # :nodoc:
135
- MigrationContext.new(migrations_paths, schema_migration)
213
+ def replica?
214
+ @config[:replica] || false
136
215
  end
137
216
 
138
- def schema_migration # :nodoc:
139
- @schema_migration ||= begin
140
- conn = self
141
- spec_name = conn.pool.pool_config.connection_specification_name
217
+ def connection_retries
218
+ (@config[:connection_retries] || 1).to_i
219
+ end
142
220
 
143
- return ActiveRecord::SchemaMigration if spec_name == "ActiveRecord::Base"
221
+ def retry_deadline
222
+ if @config[:retry_deadline]
223
+ @config[:retry_deadline].to_f
224
+ else
225
+ nil
226
+ end
227
+ end
144
228
 
145
- schema_migration_name = "#{spec_name}::SchemaMigration"
229
+ def default_timezone
230
+ @default_timezone || ActiveRecord.default_timezone
231
+ end
146
232
 
147
- Class.new(ActiveRecord::SchemaMigration) do
148
- define_singleton_method(:name) { schema_migration_name }
149
- define_singleton_method(:to_s) { schema_migration_name }
233
+ # Determines whether writes are currently being prevented.
234
+ #
235
+ # Returns true if the connection is a replica or returns
236
+ # the value of +current_preventing_writes+.
237
+ def preventing_writes?
238
+ return true if replica?
239
+ return false if connection_class.nil?
150
240
 
151
- self.connection_specification_name = spec_name
152
- end
153
- end
241
+ connection_class.current_preventing_writes
154
242
  end
155
243
 
156
244
  def prepared_statements?
@@ -159,7 +247,7 @@ module ActiveRecord
159
247
  alias :prepared_statements :prepared_statements?
160
248
 
161
249
  def prepared_statements_disabled_cache # :nodoc:
162
- Thread.current[:ar_prepared_statements_disabled_cache] ||= Set.new
250
+ ActiveSupport::IsolatedExecutionState[:active_record_prepared_statements_disabled_cache] ||= Set.new
163
251
  end
164
252
 
165
253
  class Version
@@ -189,41 +277,48 @@ module ActiveRecord
189
277
  def lease
190
278
  if in_use?
191
279
  msg = +"Cannot lease connection, "
192
- if @owner == Thread.current
280
+ if @owner == ActiveSupport::IsolatedExecutionState.context
193
281
  msg << "it is already leased by the current thread."
194
282
  else
195
283
  msg << "it is already in use by a different thread: #{@owner}. " \
196
- "Current thread: #{Thread.current}."
284
+ "Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
197
285
  end
198
286
  raise ActiveRecordError, msg
199
287
  end
200
288
 
201
- @owner = Thread.current
289
+ @owner = ActiveSupport::IsolatedExecutionState.context
202
290
  end
203
291
 
204
- def connection_klass # :nodoc:
205
- @pool.connection_klass
292
+ def connection_class # :nodoc:
293
+ @pool.connection_class
206
294
  end
207
295
 
208
- def schema_cache
209
- @pool.get_schema_cache(self)
296
+ # The role (e.g. +:writing+) for the current connection. In a
297
+ # non-multi role application, +:writing+ is returned.
298
+ def role
299
+ @pool.role
210
300
  end
211
301
 
212
- def schema_cache=(cache)
213
- cache.connection = self
214
- @pool.set_schema_cache(cache)
302
+ # The shard (e.g. +:default+) for the current connection. In
303
+ # a non-sharded application, +:default+ is returned.
304
+ def shard
305
+ @pool.shard
306
+ end
307
+
308
+ def schema_cache
309
+ @pool.schema_cache || (@schema_cache ||= BoundSchemaReflection.for_lone_connection(@pool.schema_reflection, self))
215
310
  end
216
311
 
217
312
  # this method must only be called while holding connection pool's mutex
218
313
  def expire
219
314
  if in_use?
220
- if @owner != Thread.current
315
+ if @owner != ActiveSupport::IsolatedExecutionState.context
221
316
  raise ActiveRecordError, "Cannot expire connection, " \
222
317
  "it is owned by a different thread: #{@owner}. " \
223
- "Current thread: #{Thread.current}."
318
+ "Current thread: #{ActiveSupport::IsolatedExecutionState.context}."
224
319
  end
225
320
 
226
- @idle_since = Concurrent.monotonic_time
321
+ @idle_since = Process.clock_gettime(Process::CLOCK_MONOTONIC)
227
322
  @owner = nil
228
323
  else
229
324
  raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
@@ -233,10 +328,10 @@ module ActiveRecord
233
328
  # this method must only be called while holding connection pool's mutex (and a desire for segfaults)
234
329
  def steal! # :nodoc:
235
330
  if in_use?
236
- if @owner != Thread.current
331
+ if @owner != ActiveSupport::IsolatedExecutionState.context
237
332
  pool.send :remove_connection_from_thread_cache, self, @owner
238
333
 
239
- @owner = Thread.current
334
+ @owner = ActiveSupport::IsolatedExecutionState.context
240
335
  end
241
336
  else
242
337
  raise ActiveRecordError, "Cannot steal connection, it is not currently leased."
@@ -246,7 +341,7 @@ module ActiveRecord
246
341
  # Seconds since this connection was returned to the pool
247
342
  def seconds_idle # :nodoc:
248
343
  return 0 if in_use?
249
- Concurrent.monotonic_time - @idle_since
344
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - @idle_since
250
345
  end
251
346
 
252
347
  def unprepared_statement
@@ -264,7 +359,14 @@ module ActiveRecord
264
359
 
265
360
  # Does the database for this adapter exist?
266
361
  def self.database_exists?(config)
267
- raise NotImplementedError
362
+ new(config).database_exists?
363
+ end
364
+
365
+ def database_exists?
366
+ connect!
367
+ true
368
+ rescue ActiveRecord::NoDatabaseError
369
+ false
268
370
  end
269
371
 
270
372
  # Does this adapter support DDL rollbacks in transactions? That is, would
@@ -282,6 +384,16 @@ module ActiveRecord
282
384
  false
283
385
  end
284
386
 
387
+ # Do TransactionRollbackErrors on savepoints affect the parent
388
+ # transaction?
389
+ def savepoint_errors_invalidate_transactions?
390
+ false
391
+ end
392
+
393
+ def supports_restart_db_transaction?
394
+ false
395
+ end
396
+
285
397
  # Does this adapter support application-enforced advisory locking?
286
398
  def supports_advisory_locks?
287
399
  false
@@ -308,6 +420,11 @@ module ActiveRecord
308
420
  false
309
421
  end
310
422
 
423
+ # Does this adapter support including non-key columns?
424
+ def supports_index_include?
425
+ false
426
+ end
427
+
311
428
  # Does this adapter support expression indices?
312
429
  def supports_expression_index?
313
430
  false
@@ -344,11 +461,26 @@ module ActiveRecord
344
461
  false
345
462
  end
346
463
 
464
+ # Does this adapter support creating deferrable constraints?
465
+ def supports_deferrable_constraints?
466
+ false
467
+ end
468
+
347
469
  # Does this adapter support creating check constraints?
348
470
  def supports_check_constraints?
349
471
  false
350
472
  end
351
473
 
474
+ # Does this adapter support creating exclusion constraints?
475
+ def supports_exclusion_constraints?
476
+ false
477
+ end
478
+
479
+ # Does this adapter support creating unique constraints?
480
+ def supports_unique_constraints?
481
+ false
482
+ end
483
+
352
484
  # Does this adapter support views?
353
485
  def supports_views?
354
486
  false
@@ -364,7 +496,7 @@ module ActiveRecord
364
496
  false
365
497
  end
366
498
 
367
- # Does this adapter support json data type?
499
+ # Does this adapter support JSON data type?
368
500
  def supports_json?
369
501
  false
370
502
  end
@@ -418,12 +550,49 @@ module ActiveRecord
418
550
  false
419
551
  end
420
552
 
553
+ def supports_concurrent_connections?
554
+ true
555
+ end
556
+
557
+ def supports_nulls_not_distinct?
558
+ false
559
+ end
560
+
561
+ def return_value_after_insert?(column) # :nodoc:
562
+ column.auto_populated?
563
+ end
564
+
565
+ def async_enabled? # :nodoc:
566
+ supports_concurrent_connections? &&
567
+ !ActiveRecord.async_query_executor.nil? && !pool.async_executor.nil?
568
+ end
569
+
421
570
  # This is meant to be implemented by the adapters that support extensions
422
- def disable_extension(name)
571
+ def disable_extension(name, **)
423
572
  end
424
573
 
425
574
  # This is meant to be implemented by the adapters that support extensions
426
- def enable_extension(name)
575
+ def enable_extension(name, **)
576
+ end
577
+
578
+ # This is meant to be implemented by the adapters that support custom enum types
579
+ def create_enum(*) # :nodoc:
580
+ end
581
+
582
+ # This is meant to be implemented by the adapters that support custom enum types
583
+ def drop_enum(*) # :nodoc:
584
+ end
585
+
586
+ # This is meant to be implemented by the adapters that support custom enum types
587
+ def rename_enum(*) # :nodoc:
588
+ end
589
+
590
+ # This is meant to be implemented by the adapters that support custom enum types
591
+ def add_enum_value(*) # :nodoc:
592
+ end
593
+
594
+ # This is meant to be implemented by the adapters that support custom enum types
595
+ def rename_enum_value(*) # :nodoc:
427
596
  end
428
597
 
429
598
  def advisory_locks_enabled? # :nodoc:
@@ -461,27 +630,72 @@ module ActiveRecord
461
630
  yield
462
631
  end
463
632
 
633
+ # Override to check all foreign key constraints in a database.
634
+ # The adapter should raise a +ActiveRecord::StatementInvalid+ if foreign key
635
+ # constraints are not met.
636
+ def check_all_foreign_keys_valid!
637
+ end
638
+
464
639
  # CONNECTION MANAGEMENT ====================================
465
640
 
641
+ # Checks whether the connection to the database was established. This doesn't
642
+ # include checking whether the database is actually capable of responding, i.e.
643
+ # whether the connection is stale.
644
+ def connected?
645
+ !@raw_connection.nil?
646
+ end
647
+
466
648
  # Checks whether the connection to the database is still active. This includes
467
649
  # checking whether the database is actually capable of responding, i.e. whether
468
650
  # the connection isn't stale.
469
651
  def active?
470
652
  end
471
653
 
472
- # Disconnects from the database if already connected, and establishes a
473
- # new connection with the database. Implementors should call super if they
474
- # override the default implementation.
475
- def reconnect!
476
- clear_cache!
477
- reset_transaction
654
+ # Disconnects from the database if already connected, and establishes a new
655
+ # connection with the database. Implementors should define private #reconnect
656
+ # instead.
657
+ def reconnect!(restore_transactions: false)
658
+ retries_available = connection_retries
659
+ deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
660
+
661
+ @lock.synchronize do
662
+ reconnect
663
+
664
+ enable_lazy_transactions!
665
+ @raw_connection_dirty = false
666
+ @verified = true
667
+
668
+ reset_transaction(restore: restore_transactions) do
669
+ clear_cache!(new_connection: true)
670
+ configure_connection
671
+ end
672
+ rescue => original_exception
673
+ translated_exception = translate_exception_class(original_exception, nil, nil)
674
+ retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
675
+
676
+ if !retry_deadline_exceeded && retries_available > 0
677
+ retries_available -= 1
678
+
679
+ if retryable_connection_error?(translated_exception)
680
+ backoff(connection_retries - retries_available)
681
+ retry
682
+ end
683
+ end
684
+
685
+ @verified = false
686
+
687
+ raise translated_exception
688
+ end
478
689
  end
479
690
 
480
691
  # Disconnects from the database if already connected. Otherwise, this
481
692
  # method does nothing.
482
693
  def disconnect!
483
- clear_cache!
484
- reset_transaction
694
+ @lock.synchronize do
695
+ clear_cache!(new_connection: true)
696
+ reset_transaction
697
+ @raw_connection_dirty = false
698
+ end
485
699
  end
486
700
 
487
701
  # Immediately forget this connection ever existed. Unlike disconnect!,
@@ -492,22 +706,20 @@ module ActiveRecord
492
706
  # rid of a connection that belonged to its parent.
493
707
  def discard!
494
708
  # This should be overridden by concrete adapters.
495
- #
496
- # Prevent @connection's finalizer from touching the socket, or
497
- # otherwise communicating with its server, when it is collected.
498
- if schema_cache.connection == self
499
- schema_cache.connection = nil
500
- end
501
709
  end
502
710
 
503
711
  # Reset the state of this connection, directing the DBMS to clear
504
712
  # transactions and other connection-related server-side state. Usually a
505
713
  # database-dependent operation.
506
714
  #
507
- # The default implementation does nothing; the implementation should be
508
- # overridden by concrete adapters.
715
+ # If a database driver or protocol does not support such a feature,
716
+ # implementors may alias this to #reconnect!. Otherwise, implementors
717
+ # should call super immediately after resetting the connection (and while
718
+ # still holding @lock).
509
719
  def reset!
510
- # this should be overridden by concrete adapters
720
+ clear_cache!(new_connection: true)
721
+ reset_transaction
722
+ configure_connection
511
723
  end
512
724
 
513
725
  # Removes the connection from the pool and disconnect it.
@@ -517,8 +729,16 @@ module ActiveRecord
517
729
  end
518
730
 
519
731
  # Clear any caching the database adapter may be doing.
520
- def clear_cache!
521
- @lock.synchronize { @statements.clear } if @statements
732
+ def clear_cache!(new_connection: false)
733
+ if @statements
734
+ @lock.synchronize do
735
+ if new_connection
736
+ @statements.reset
737
+ else
738
+ @statements.clear
739
+ end
740
+ end
741
+ end
522
742
  end
523
743
 
524
744
  # Returns true if its required to reload the connection between requests for development mode.
@@ -530,7 +750,31 @@ module ActiveRecord
530
750
  # This is done under the hood by calling #active?. If the connection
531
751
  # is no longer active, then this method will reconnect to the database.
532
752
  def verify!
533
- reconnect! unless active?
753
+ unless active?
754
+ @lock.synchronize do
755
+ if @unconfigured_connection
756
+ @raw_connection = @unconfigured_connection
757
+ @unconfigured_connection = nil
758
+ configure_connection
759
+ @verified = true
760
+ return
761
+ end
762
+
763
+ reconnect!(restore_transactions: true)
764
+ end
765
+ end
766
+
767
+ @verified = true
768
+ end
769
+
770
+ def connect!
771
+ verify!
772
+ self
773
+ end
774
+
775
+ def clean! # :nodoc:
776
+ @raw_connection_dirty = false
777
+ @verified = nil
534
778
  end
535
779
 
536
780
  # Provides access to the underlying database driver for this adapter. For
@@ -539,9 +783,16 @@ module ActiveRecord
539
783
  #
540
784
  # This is useful for when you need to call a proprietary method such as
541
785
  # PostgreSQL's lo_* methods.
786
+ #
787
+ # Active Record cannot track if the database is getting modified using
788
+ # this client. If that is the case, generally you'll want to invalidate
789
+ # the query cache using +ActiveRecord::Base.clear_query_cache+.
542
790
  def raw_connection
543
- disable_lazy_transactions!
544
- @connection
791
+ with_raw_connection do |conn|
792
+ disable_lazy_transactions!
793
+ @raw_connection_dirty = true
794
+ conn
795
+ end
545
796
  end
546
797
 
547
798
  def default_uniqueness_comparison(attribute, value) # :nodoc:
@@ -593,84 +844,265 @@ module ActiveRecord
593
844
  end
594
845
 
595
846
  def database_version # :nodoc:
596
- schema_cache.database_version
847
+ pool.server_version(self)
597
848
  end
598
849
 
599
850
  def check_version # :nodoc:
600
851
  end
601
852
 
602
- private
603
- def type_map
604
- @type_map ||= Type::TypeMap.new.tap do |mapping|
605
- initialize_type_map(mapping)
853
+ # Returns the version identifier of the schema currently available in
854
+ # the database. This is generally equal to the number of the highest-
855
+ # numbered migration that has been executed, or 0 if no schema
856
+ # information is present / the database is empty.
857
+ def schema_version
858
+ pool.migration_context.current_version
859
+ end
860
+
861
+ class << self
862
+ def register_class_with_precision(mapping, key, klass, **kwargs) # :nodoc:
863
+ mapping.register_type(key) do |*args|
864
+ precision = extract_precision(args.last)
865
+ klass.new(precision: precision, **kwargs)
866
+ end
867
+ end
868
+
869
+ def extended_type_map(default_timezone:) # :nodoc:
870
+ Type::TypeMap.new(self::TYPE_MAP).tap do |m|
871
+ register_class_with_precision m, %r(\A[^\(]*time)i, Type::Time, timezone: default_timezone
872
+ register_class_with_precision m, %r(\A[^\(]*datetime)i, Type::DateTime, timezone: default_timezone
873
+ m.alias_type %r(\A[^\(]*timestamp)i, "datetime"
874
+ end
875
+ end
876
+
877
+ private
878
+ def initialize_type_map(m)
879
+ register_class_with_limit m, %r(boolean)i, Type::Boolean
880
+ register_class_with_limit m, %r(char)i, Type::String
881
+ register_class_with_limit m, %r(binary)i, Type::Binary
882
+ register_class_with_limit m, %r(text)i, Type::Text
883
+ register_class_with_precision m, %r(date)i, Type::Date
884
+ register_class_with_precision m, %r(time)i, Type::Time
885
+ register_class_with_precision m, %r(datetime)i, Type::DateTime
886
+ register_class_with_limit m, %r(float)i, Type::Float
887
+ register_class_with_limit m, %r(int)i, Type::Integer
888
+
889
+ m.alias_type %r(blob)i, "binary"
890
+ m.alias_type %r(clob)i, "text"
891
+ m.alias_type %r(timestamp)i, "datetime"
892
+ m.alias_type %r(numeric)i, "decimal"
893
+ m.alias_type %r(number)i, "decimal"
894
+ m.alias_type %r(double)i, "float"
895
+
896
+ m.register_type %r(^json)i, Type::Json.new
897
+
898
+ m.register_type(%r(decimal)i) do |sql_type|
899
+ scale = extract_scale(sql_type)
900
+ precision = extract_precision(sql_type)
901
+
902
+ if scale == 0
903
+ # FIXME: Remove this class as well
904
+ Type::DecimalWithoutScale.new(precision: precision)
905
+ else
906
+ Type::Decimal.new(precision: precision, scale: scale)
907
+ end
908
+ end
909
+ end
910
+
911
+ def register_class_with_limit(mapping, key, klass)
912
+ mapping.register_type(key) do |*args|
913
+ limit = extract_limit(args.last)
914
+ klass.new(limit: limit)
915
+ end
916
+ end
917
+
918
+ def extract_scale(sql_type)
919
+ case sql_type
920
+ when /\((\d+)\)/ then 0
921
+ when /\((\d+)(,(\d+))\)/ then $3.to_i
922
+ end
923
+ end
924
+
925
+ def extract_precision(sql_type)
926
+ $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
606
927
  end
928
+
929
+ def extract_limit(sql_type)
930
+ $1.to_i if sql_type =~ /\((.*)\)/
931
+ end
932
+ end
933
+
934
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
935
+ EXTENDED_TYPE_MAPS = Concurrent::Map.new
936
+
937
+ private
938
+ def reconnect_can_restore_state?
939
+ transaction_manager.restorable? && !@raw_connection_dirty
607
940
  end
608
941
 
609
- def initialize_type_map(m = type_map)
610
- register_class_with_limit m, %r(boolean)i, Type::Boolean
611
- register_class_with_limit m, %r(char)i, Type::String
612
- register_class_with_limit m, %r(binary)i, Type::Binary
613
- register_class_with_limit m, %r(text)i, Type::Text
614
- register_class_with_precision m, %r(date)i, Type::Date
615
- register_class_with_precision m, %r(time)i, Type::Time
616
- register_class_with_precision m, %r(datetime)i, Type::DateTime
617
- register_class_with_limit m, %r(float)i, Type::Float
618
- register_class_with_limit m, %r(int)i, Type::Integer
619
-
620
- m.alias_type %r(blob)i, "binary"
621
- m.alias_type %r(clob)i, "text"
622
- m.alias_type %r(timestamp)i, "datetime"
623
- m.alias_type %r(numeric)i, "decimal"
624
- m.alias_type %r(number)i, "decimal"
625
- m.alias_type %r(double)i, "float"
626
-
627
- m.register_type %r(^json)i, Type::Json.new
628
-
629
- m.register_type(%r(decimal)i) do |sql_type|
630
- scale = extract_scale(sql_type)
631
- precision = extract_precision(sql_type)
632
-
633
- if scale == 0
634
- # FIXME: Remove this class as well
635
- Type::DecimalWithoutScale.new(precision: precision)
942
+ # Lock the monitor, ensure we're properly connected and
943
+ # transactions are materialized, and then yield the underlying
944
+ # raw connection object.
945
+ #
946
+ # If +allow_retry+ is true, a connection-related exception will
947
+ # cause an automatic reconnect and re-run of the block, up to
948
+ # the connection's configured +connection_retries+ setting
949
+ # and the configured +retry_deadline+ limit. (Note that when
950
+ # +allow_retry+ is true, it's possible to return without having marked
951
+ # the connection as verified. If the block is guaranteed to exercise the
952
+ # connection, consider calling `verified!` to avoid needless
953
+ # verification queries in subsequent calls.)
954
+ #
955
+ # If +materialize_transactions+ is false, the block will be run without
956
+ # ensuring virtual transactions have been materialized in the DB
957
+ # server's state. The active transaction will also remain clean
958
+ # (if it is not already dirty), meaning it's able to be restored
959
+ # by reconnecting and opening an equivalent-depth set of new
960
+ # transactions. This should only be used by transaction control
961
+ # methods, and internal transaction-agnostic queries.
962
+ #
963
+ ###
964
+ #
965
+ # It's not the primary use case, so not something to optimize
966
+ # for, but note that this method does need to be re-entrant:
967
+ # +materialize_transactions+ will re-enter if it has work to do,
968
+ # and the yield block can also do so under some circumstances.
969
+ #
970
+ # In the latter case, we really ought to guarantee the inner
971
+ # call will not reconnect (which would interfere with the
972
+ # still-yielded connection in the outer block), but we currently
973
+ # provide no special enforcement there.
974
+ #
975
+ def with_raw_connection(allow_retry: false, materialize_transactions: true)
976
+ @lock.synchronize do
977
+ connect! if @raw_connection.nil? && reconnect_can_restore_state?
978
+
979
+ self.materialize_transactions if materialize_transactions
980
+
981
+ retries_available = allow_retry ? connection_retries : 0
982
+ deadline = retry_deadline && Process.clock_gettime(Process::CLOCK_MONOTONIC) + retry_deadline
983
+ reconnectable = reconnect_can_restore_state?
984
+
985
+ if @verified
986
+ # Cool, we're confident the connection's ready to use. (Note this might have
987
+ # become true during the above #materialize_transactions.)
988
+ elsif reconnectable
989
+ if allow_retry
990
+ # Not sure about the connection yet, but if anything goes wrong we can
991
+ # just reconnect and re-run our query
992
+ else
993
+ # We can reconnect if needed, but we don't trust the upcoming query to be
994
+ # safely re-runnable: let's verify the connection to be sure
995
+ verify!
996
+ end
636
997
  else
637
- Type::Decimal.new(precision: precision, scale: scale)
998
+ # We don't know whether the connection is okay, but it also doesn't matter:
999
+ # we wouldn't be able to reconnect anyway. We're just going to run our query
1000
+ # and hope for the best.
1001
+ end
1002
+
1003
+ begin
1004
+ yield @raw_connection
1005
+ rescue => original_exception
1006
+ translated_exception = translate_exception_class(original_exception, nil, nil)
1007
+ invalidate_transaction(translated_exception)
1008
+ retry_deadline_exceeded = deadline && deadline < Process.clock_gettime(Process::CLOCK_MONOTONIC)
1009
+
1010
+ if !retry_deadline_exceeded && retries_available > 0
1011
+ retries_available -= 1
1012
+
1013
+ if retryable_query_error?(translated_exception)
1014
+ backoff(connection_retries - retries_available)
1015
+ retry
1016
+ elsif reconnectable && retryable_connection_error?(translated_exception)
1017
+ reconnect!(restore_transactions: true)
1018
+ # Only allowed to reconnect once, because reconnect! has its own retry
1019
+ # loop
1020
+ reconnectable = false
1021
+ retry
1022
+ end
1023
+ end
1024
+
1025
+ unless retryable_query_error?(translated_exception)
1026
+ # Barring a known-retryable error inside the query (regardless of
1027
+ # whether we were in a _position_ to retry it), we should infer that
1028
+ # there's likely a real problem with the connection.
1029
+ @verified = false
1030
+ end
1031
+
1032
+ raise translated_exception
1033
+ ensure
1034
+ dirty_current_transaction if materialize_transactions
638
1035
  end
639
1036
  end
640
1037
  end
641
1038
 
642
- def reload_type_map
643
- type_map.clear
644
- initialize_type_map
1039
+ # Mark the connection as verified. Call this inside a
1040
+ # `with_raw_connection` block only when the block is guaranteed to
1041
+ # exercise the raw connection.
1042
+ def verified!
1043
+ @verified = true
645
1044
  end
646
1045
 
647
- def register_class_with_limit(mapping, key, klass)
648
- mapping.register_type(key) do |*args|
649
- limit = extract_limit(args.last)
650
- klass.new(limit: limit)
651
- end
1046
+ def retryable_connection_error?(exception)
1047
+ exception.is_a?(ConnectionNotEstablished) || exception.is_a?(ConnectionFailed)
652
1048
  end
653
1049
 
654
- def register_class_with_precision(mapping, key, klass)
655
- mapping.register_type(key) do |*args|
656
- precision = extract_precision(args.last)
657
- klass.new(precision: precision)
658
- end
1050
+ def invalidate_transaction(exception)
1051
+ return unless exception.is_a?(TransactionRollbackError)
1052
+ return unless savepoint_errors_invalidate_transactions?
1053
+
1054
+ current_transaction.invalidate!
659
1055
  end
660
1056
 
661
- def extract_scale(sql_type)
662
- case sql_type
663
- when /\((\d+)\)/ then 0
664
- when /\((\d+)(,(\d+))\)/ then $3.to_i
665
- end
1057
+ def retryable_query_error?(exception)
1058
+ # We definitely can't retry if we were inside an invalidated transaction.
1059
+ return false if current_transaction.invalidated?
1060
+
1061
+ exception.is_a?(Deadlocked) || exception.is_a?(LockWaitTimeout)
666
1062
  end
667
1063
 
668
- def extract_precision(sql_type)
669
- $1.to_i if sql_type =~ /\((\d+)(,\d+)?\)/
1064
+ def backoff(counter)
1065
+ sleep 0.1 * counter
670
1066
  end
671
1067
 
672
- def extract_limit(sql_type)
673
- $1.to_i if sql_type =~ /\((.*)\)/
1068
+ def reconnect
1069
+ raise NotImplementedError
1070
+ end
1071
+
1072
+ # Returns a raw connection for internal use with methods that are known
1073
+ # to both be thread-safe and not rely upon actual server communication.
1074
+ # This is useful for e.g. string escaping methods.
1075
+ def any_raw_connection
1076
+ @raw_connection || valid_raw_connection
1077
+ end
1078
+
1079
+ # Similar to any_raw_connection, but ensures it is validated and
1080
+ # connected. Any method called on this result still needs to be
1081
+ # independently thread-safe, so it probably shouldn't talk to the
1082
+ # server... but some drivers fail if they know the connection has gone
1083
+ # away.
1084
+ def valid_raw_connection
1085
+ (@verified && @raw_connection) ||
1086
+ # `allow_retry: false`, to force verification: the block won't
1087
+ # raise, so a retry wouldn't help us get the valid connection we
1088
+ # need.
1089
+ with_raw_connection(allow_retry: false, materialize_transactions: false) { |conn| conn }
1090
+ end
1091
+
1092
+ def extended_type_map_key
1093
+ if @default_timezone
1094
+ { default_timezone: @default_timezone }
1095
+ end
1096
+ end
1097
+
1098
+ def type_map
1099
+ if key = extended_type_map_key
1100
+ self.class::EXTENDED_TYPE_MAPS.compute_if_absent(key) do
1101
+ self.class.extended_type_map(**key)
1102
+ end
1103
+ else
1104
+ self.class::TYPE_MAP
1105
+ end
674
1106
  end
675
1107
 
676
1108
  def translate_exception_class(e, sql, binds)
@@ -683,7 +1115,7 @@ module ActiveRecord
683
1115
  exception
684
1116
  end
685
1117
 
686
- def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil) # :doc:
1118
+ def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false, &block) # :doc:
687
1119
  @instrumenter.instrument(
688
1120
  "sql.active_record",
689
1121
  sql: sql,
@@ -691,22 +1123,30 @@ module ActiveRecord
691
1123
  binds: binds,
692
1124
  type_casted_binds: type_casted_binds,
693
1125
  statement_name: statement_name,
694
- connection: self) do
695
- @lock.synchronize do
696
- yield
697
- end
698
- rescue => e
699
- raise translate_exception_class(e, sql, binds)
1126
+ async: async,
1127
+ connection: self,
1128
+ transaction: current_transaction.user_transaction.presence,
1129
+ row_count: 0,
1130
+ &block
1131
+ )
1132
+ rescue ActiveRecord::StatementInvalid => ex
1133
+ raise ex.set_query(sql, binds)
1134
+ end
1135
+
1136
+ def transform_query(sql)
1137
+ ActiveRecord.query_transformers.each do |transformer|
1138
+ sql = transformer.call(sql, self)
700
1139
  end
1140
+ sql
701
1141
  end
702
1142
 
703
1143
  def translate_exception(exception, message:, sql:, binds:)
704
1144
  # override in derived class
705
1145
  case exception
706
- when RuntimeError
1146
+ when RuntimeError, ActiveRecord::ActiveRecordError
707
1147
  exception
708
1148
  else
709
- ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds)
1149
+ ActiveRecord::StatementInvalid.new(message, sql: sql, binds: binds, connection_pool: @pool)
710
1150
  end
711
1151
  end
712
1152
 
@@ -750,9 +1190,30 @@ module ActiveRecord
750
1190
  #
751
1191
  # This is an internal hook to make possible connection adapters to build
752
1192
  # custom result objects with connection-specific data.
753
- def build_result(columns:, rows:, column_types: {})
1193
+ def build_result(columns:, rows:, column_types: nil)
754
1194
  ActiveRecord::Result.new(columns, rows, column_types)
755
1195
  end
1196
+
1197
+ # Perform any necessary initialization upon the newly-established
1198
+ # @raw_connection -- this is the place to modify the adapter's
1199
+ # connection settings, run queries to configure any application-global
1200
+ # "session" variables, etc.
1201
+ #
1202
+ # Implementations may assume this method will only be called while
1203
+ # holding @lock (or from #initialize).
1204
+ def configure_connection
1205
+ check_version
1206
+ end
1207
+
1208
+ def default_prepared_statements
1209
+ true
1210
+ end
1211
+
1212
+ def warning_ignored?(warning)
1213
+ ActiveRecord.db_warnings_ignore.any? do |warning_matcher|
1214
+ warning.message.match?(warning_matcher) || warning.code.to_s.match?(warning_matcher)
1215
+ end
1216
+ end
756
1217
  end
757
1218
  end
758
1219
  end