activerecord 5.2.8 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (364) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1393 -587
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +10 -9
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +122 -47
  10. data/lib/active_record/associations/association_scope.rb +24 -24
  11. data/lib/active_record/associations/belongs_to_association.rb +67 -49
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -7
  13. data/lib/active_record/associations/builder/association.rb +52 -23
  14. data/lib/active_record/associations/builder/belongs_to.rb +44 -61
  15. data/lib/active_record/associations/builder/collection_association.rb +17 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +10 -3
  18. data/lib/active_record/associations/builder/has_one.rb +35 -3
  19. data/lib/active_record/associations/builder/singular_association.rb +5 -3
  20. data/lib/active_record/associations/collection_association.rb +59 -50
  21. data/lib/active_record/associations/collection_proxy.rb +32 -23
  22. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  23. data/lib/active_record/associations/foreign_association.rb +20 -0
  24. data/lib/active_record/associations/has_many_association.rb +27 -14
  25. data/lib/active_record/associations/has_many_through_association.rb +26 -19
  26. data/lib/active_record/associations/has_one_association.rb +52 -37
  27. data/lib/active_record/associations/has_one_through_association.rb +6 -6
  28. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  29. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  30. data/lib/active_record/associations/join_dependency.rb +97 -62
  31. data/lib/active_record/associations/preloader/association.rb +220 -60
  32. data/lib/active_record/associations/preloader/batch.rb +48 -0
  33. data/lib/active_record/associations/preloader/branch.rb +147 -0
  34. data/lib/active_record/associations/preloader/through_association.rb +85 -40
  35. data/lib/active_record/associations/preloader.rb +44 -105
  36. data/lib/active_record/associations/singular_association.rb +9 -17
  37. data/lib/active_record/associations/through_association.rb +4 -4
  38. data/lib/active_record/associations.rb +207 -66
  39. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  40. data/lib/active_record/attribute_assignment.rb +17 -19
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +19 -8
  42. data/lib/active_record/attribute_methods/dirty.rb +141 -47
  43. data/lib/active_record/attribute_methods/primary_key.rb +22 -27
  44. data/lib/active_record/attribute_methods/query.rb +6 -10
  45. data/lib/active_record/attribute_methods/read.rb +15 -55
  46. data/lib/active_record/attribute_methods/serialization.rb +77 -18
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +16 -18
  48. data/lib/active_record/attribute_methods/write.rb +18 -37
  49. data/lib/active_record/attribute_methods.rb +90 -153
  50. data/lib/active_record/attributes.rb +38 -12
  51. data/lib/active_record/autosave_association.rb +50 -50
  52. data/lib/active_record/base.rb +23 -18
  53. data/lib/active_record/callbacks.rb +159 -44
  54. data/lib/active_record/coders/yaml_column.rb +12 -3
  55. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +292 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +209 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +76 -0
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +92 -464
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -51
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +209 -164
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +38 -22
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +103 -82
  63. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +140 -110
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +236 -94
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +16 -5
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +456 -159
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +169 -78
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +367 -162
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +311 -327
  71. data/lib/active_record/connection_adapters/column.rb +33 -11
  72. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  73. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  74. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  75. data/lib/active_record/connection_adapters/mysql/database_statements.rb +113 -45
  76. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  77. data/lib/active_record/connection_adapters/mysql/quoting.rb +71 -5
  78. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  79. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  80. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +25 -8
  81. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +143 -19
  82. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  83. data/lib/active_record/connection_adapters/mysql2_adapter.rb +63 -22
  84. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  85. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  86. data/lib/active_record/connection_adapters/postgresql/column.rb +53 -28
  87. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +56 -63
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  91. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +10 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +15 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +54 -16
  95. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  97. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  102. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +26 -12
  105. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  106. data/lib/active_record/connection_adapters/postgresql/oid.rb +4 -0
  107. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -52
  108. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +34 -2
  109. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +39 -4
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +128 -91
  111. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +25 -1
  112. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +149 -113
  113. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  114. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  115. data/lib/active_record/connection_adapters/postgresql_adapter.rb +386 -182
  116. data/lib/active_record/connection_adapters/schema_cache.rb +161 -22
  117. data/lib/active_record/connection_adapters/sql_type_metadata.rb +17 -6
  118. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +152 -0
  119. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +65 -18
  120. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  121. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +92 -26
  122. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +251 -204
  123. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  124. data/lib/active_record/connection_adapters.rb +53 -0
  125. data/lib/active_record/connection_handling.rb +292 -38
  126. data/lib/active_record/core.rb +385 -158
  127. data/lib/active_record/counter_cache.rb +8 -30
  128. data/lib/active_record/database_configurations/connection_url_resolver.rb +100 -0
  129. data/lib/active_record/database_configurations/database_config.rb +83 -0
  130. data/lib/active_record/database_configurations/hash_config.rb +154 -0
  131. data/lib/active_record/database_configurations/url_config.rb +53 -0
  132. data/lib/active_record/database_configurations.rb +256 -0
  133. data/lib/active_record/delegated_type.rb +250 -0
  134. data/lib/active_record/destroy_association_async_job.rb +36 -0
  135. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  136. data/lib/active_record/dynamic_matchers.rb +4 -5
  137. data/lib/active_record/encryption/cipher/aes256_gcm.rb +98 -0
  138. data/lib/active_record/encryption/cipher.rb +53 -0
  139. data/lib/active_record/encryption/config.rb +44 -0
  140. data/lib/active_record/encryption/configurable.rb +61 -0
  141. data/lib/active_record/encryption/context.rb +35 -0
  142. data/lib/active_record/encryption/contexts.rb +72 -0
  143. data/lib/active_record/encryption/derived_secret_key_provider.rb +12 -0
  144. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  145. data/lib/active_record/encryption/encryptable_record.rb +208 -0
  146. data/lib/active_record/encryption/encrypted_attribute_type.rb +140 -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 +155 -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 +160 -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 +42 -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_serializer.rb +90 -0
  159. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  160. data/lib/active_record/encryption/properties.rb +76 -0
  161. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  162. data/lib/active_record/encryption/scheme.rb +99 -0
  163. data/lib/active_record/encryption.rb +55 -0
  164. data/lib/active_record/enum.rb +130 -51
  165. data/lib/active_record/errors.rb +129 -23
  166. data/lib/active_record/explain.rb +10 -6
  167. data/lib/active_record/explain_registry.rb +11 -6
  168. data/lib/active_record/explain_subscriber.rb +1 -1
  169. data/lib/active_record/fixture_set/file.rb +22 -15
  170. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  171. data/lib/active_record/fixture_set/render_context.rb +17 -0
  172. data/lib/active_record/fixture_set/table_row.rb +187 -0
  173. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  174. data/lib/active_record/fixtures.rb +206 -490
  175. data/lib/active_record/future_result.rb +139 -0
  176. data/lib/active_record/gem_version.rb +3 -3
  177. data/lib/active_record/inheritance.rb +104 -37
  178. data/lib/active_record/insert_all.rb +278 -0
  179. data/lib/active_record/integration.rb +69 -18
  180. data/lib/active_record/internal_metadata.rb +24 -9
  181. data/lib/active_record/legacy_yaml_adapter.rb +3 -36
  182. data/lib/active_record/locking/optimistic.rb +41 -26
  183. data/lib/active_record/locking/pessimistic.rb +18 -8
  184. data/lib/active_record/log_subscriber.rb +46 -35
  185. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  186. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  187. data/lib/active_record/middleware/database_selector.rb +82 -0
  188. data/lib/active_record/middleware/shard_selector.rb +60 -0
  189. data/lib/active_record/migration/command_recorder.rb +96 -44
  190. data/lib/active_record/migration/compatibility.rb +246 -64
  191. data/lib/active_record/migration/join_table.rb +1 -2
  192. data/lib/active_record/migration.rb +266 -187
  193. data/lib/active_record/model_schema.rb +165 -52
  194. data/lib/active_record/nested_attributes.rb +17 -19
  195. data/lib/active_record/no_touching.rb +11 -4
  196. data/lib/active_record/null_relation.rb +2 -7
  197. data/lib/active_record/persistence.rb +467 -92
  198. data/lib/active_record/query_cache.rb +21 -4
  199. data/lib/active_record/query_logs.rb +138 -0
  200. data/lib/active_record/querying.rb +51 -24
  201. data/lib/active_record/railtie.rb +224 -57
  202. data/lib/active_record/railties/console_sandbox.rb +2 -4
  203. data/lib/active_record/railties/controller_runtime.rb +31 -36
  204. data/lib/active_record/railties/databases.rake +369 -101
  205. data/lib/active_record/readonly_attributes.rb +15 -0
  206. data/lib/active_record/reflection.rb +170 -137
  207. data/lib/active_record/relation/batches/batch_enumerator.rb +44 -14
  208. data/lib/active_record/relation/batches.rb +46 -37
  209. data/lib/active_record/relation/calculations.rb +168 -96
  210. data/lib/active_record/relation/delegation.rb +37 -52
  211. data/lib/active_record/relation/finder_methods.rb +79 -58
  212. data/lib/active_record/relation/from_clause.rb +5 -1
  213. data/lib/active_record/relation/merger.rb +50 -51
  214. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  215. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  216. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  217. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  218. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  219. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  220. data/lib/active_record/relation/predicate_builder.rb +58 -46
  221. data/lib/active_record/relation/query_attribute.rb +9 -10
  222. data/lib/active_record/relation/query_methods.rb +685 -208
  223. data/lib/active_record/relation/record_fetch_warning.rb +9 -11
  224. data/lib/active_record/relation/spawn_methods.rb +10 -10
  225. data/lib/active_record/relation/where_clause.rb +108 -64
  226. data/lib/active_record/relation.rb +515 -151
  227. data/lib/active_record/result.rb +78 -42
  228. data/lib/active_record/runtime_registry.rb +9 -13
  229. data/lib/active_record/sanitization.rb +29 -44
  230. data/lib/active_record/schema.rb +37 -31
  231. data/lib/active_record/schema_dumper.rb +74 -23
  232. data/lib/active_record/schema_migration.rb +7 -9
  233. data/lib/active_record/scoping/default.rb +62 -17
  234. data/lib/active_record/scoping/named.rb +17 -32
  235. data/lib/active_record/scoping.rb +70 -41
  236. data/lib/active_record/secure_token.rb +16 -8
  237. data/lib/active_record/serialization.rb +6 -4
  238. data/lib/active_record/signed_id.rb +116 -0
  239. data/lib/active_record/statement_cache.rb +49 -6
  240. data/lib/active_record/store.rb +88 -9
  241. data/lib/active_record/suppressor.rb +13 -17
  242. data/lib/active_record/table_metadata.rb +42 -43
  243. data/lib/active_record/tasks/database_tasks.rb +352 -94
  244. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  245. data/lib/active_record/tasks/postgresql_database_tasks.rb +41 -39
  246. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  247. data/lib/active_record/test_databases.rb +24 -0
  248. data/lib/active_record/test_fixtures.rb +287 -0
  249. data/lib/active_record/timestamp.rb +44 -34
  250. data/lib/active_record/touch_later.rb +23 -22
  251. data/lib/active_record/transactions.rb +67 -128
  252. data/lib/active_record/translation.rb +3 -3
  253. data/lib/active_record/type/adapter_specific_registry.rb +34 -19
  254. data/lib/active_record/type/hash_lookup_type_map.rb +34 -2
  255. data/lib/active_record/type/internal/timezone.rb +2 -2
  256. data/lib/active_record/type/serialized.rb +7 -4
  257. data/lib/active_record/type/time.rb +10 -0
  258. data/lib/active_record/type/type_map.rb +17 -21
  259. data/lib/active_record/type/unsigned_integer.rb +0 -1
  260. data/lib/active_record/type.rb +9 -5
  261. data/lib/active_record/type_caster/connection.rb +15 -15
  262. data/lib/active_record/type_caster/map.rb +8 -8
  263. data/lib/active_record/validations/associated.rb +2 -3
  264. data/lib/active_record/validations/numericality.rb +35 -0
  265. data/lib/active_record/validations/uniqueness.rb +39 -31
  266. data/lib/active_record/validations.rb +4 -3
  267. data/lib/active_record.rb +209 -32
  268. data/lib/arel/alias_predication.rb +9 -0
  269. data/lib/arel/attributes/attribute.rb +33 -0
  270. data/lib/arel/collectors/bind.rb +29 -0
  271. data/lib/arel/collectors/composite.rb +39 -0
  272. data/lib/arel/collectors/plain_string.rb +20 -0
  273. data/lib/arel/collectors/sql_string.rb +27 -0
  274. data/lib/arel/collectors/substitute_binds.rb +35 -0
  275. data/lib/arel/crud.rb +48 -0
  276. data/lib/arel/delete_manager.rb +32 -0
  277. data/lib/arel/errors.rb +9 -0
  278. data/lib/arel/expressions.rb +29 -0
  279. data/lib/arel/factory_methods.rb +49 -0
  280. data/lib/arel/filter_predications.rb +9 -0
  281. data/lib/arel/insert_manager.rb +48 -0
  282. data/lib/arel/math.rb +45 -0
  283. data/lib/arel/nodes/and.rb +32 -0
  284. data/lib/arel/nodes/ascending.rb +23 -0
  285. data/lib/arel/nodes/binary.rb +126 -0
  286. data/lib/arel/nodes/bind_param.rb +44 -0
  287. data/lib/arel/nodes/case.rb +55 -0
  288. data/lib/arel/nodes/casted.rb +62 -0
  289. data/lib/arel/nodes/comment.rb +29 -0
  290. data/lib/arel/nodes/count.rb +12 -0
  291. data/lib/arel/nodes/delete_statement.rb +44 -0
  292. data/lib/arel/nodes/descending.rb +23 -0
  293. data/lib/arel/nodes/equality.rb +15 -0
  294. data/lib/arel/nodes/extract.rb +24 -0
  295. data/lib/arel/nodes/false.rb +16 -0
  296. data/lib/arel/nodes/filter.rb +10 -0
  297. data/lib/arel/nodes/full_outer_join.rb +8 -0
  298. data/lib/arel/nodes/function.rb +45 -0
  299. data/lib/arel/nodes/grouping.rb +11 -0
  300. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  301. data/lib/arel/nodes/in.rb +15 -0
  302. data/lib/arel/nodes/infix_operation.rb +92 -0
  303. data/lib/arel/nodes/inner_join.rb +8 -0
  304. data/lib/arel/nodes/insert_statement.rb +37 -0
  305. data/lib/arel/nodes/join_source.rb +20 -0
  306. data/lib/arel/nodes/matches.rb +18 -0
  307. data/lib/arel/nodes/named_function.rb +23 -0
  308. data/lib/arel/nodes/node.rb +51 -0
  309. data/lib/arel/nodes/node_expression.rb +13 -0
  310. data/lib/arel/nodes/ordering.rb +27 -0
  311. data/lib/arel/nodes/outer_join.rb +8 -0
  312. data/lib/arel/nodes/over.rb +15 -0
  313. data/lib/arel/nodes/regexp.rb +16 -0
  314. data/lib/arel/nodes/right_outer_join.rb +8 -0
  315. data/lib/arel/nodes/select_core.rb +67 -0
  316. data/lib/arel/nodes/select_statement.rb +41 -0
  317. data/lib/arel/nodes/sql_literal.rb +19 -0
  318. data/lib/arel/nodes/string_join.rb +11 -0
  319. data/lib/arel/nodes/table_alias.rb +31 -0
  320. data/lib/arel/nodes/terminal.rb +16 -0
  321. data/lib/arel/nodes/true.rb +16 -0
  322. data/lib/arel/nodes/unary.rb +44 -0
  323. data/lib/arel/nodes/unary_operation.rb +20 -0
  324. data/lib/arel/nodes/unqualified_column.rb +22 -0
  325. data/lib/arel/nodes/update_statement.rb +46 -0
  326. data/lib/arel/nodes/values_list.rb +9 -0
  327. data/lib/arel/nodes/window.rb +126 -0
  328. data/lib/arel/nodes/with.rb +11 -0
  329. data/lib/arel/nodes.rb +71 -0
  330. data/lib/arel/order_predications.rb +13 -0
  331. data/lib/arel/predications.rb +258 -0
  332. data/lib/arel/select_manager.rb +276 -0
  333. data/lib/arel/table.rb +117 -0
  334. data/lib/arel/tree_manager.rb +60 -0
  335. data/lib/arel/update_manager.rb +48 -0
  336. data/lib/arel/visitors/dot.rb +298 -0
  337. data/lib/arel/visitors/mysql.rb +99 -0
  338. data/lib/arel/visitors/postgresql.rb +110 -0
  339. data/lib/arel/visitors/sqlite.rb +38 -0
  340. data/lib/arel/visitors/to_sql.rb +955 -0
  341. data/lib/arel/visitors/visitor.rb +45 -0
  342. data/lib/arel/visitors.rb +13 -0
  343. data/lib/arel/window_predications.rb +9 -0
  344. data/lib/arel.rb +55 -0
  345. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  346. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  347. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  348. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  349. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  350. data/lib/rails/generators/active_record/migration.rb +19 -2
  351. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  352. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  353. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  354. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  355. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  356. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  357. metadata +162 -32
  358. data/lib/active_record/attribute_decorators.rb +0 -90
  359. data/lib/active_record/collection_cache_key.rb +0 -53
  360. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  361. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  362. data/lib/active_record/define_callbacks.rb +0 -22
  363. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  364. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/hash/slice"
3
4
  require "active_support/core_ext/object/deep_dup"
4
5
 
5
6
  module ActiveRecord
@@ -7,7 +8,7 @@ module ActiveRecord
7
8
  # but can be queried by name. Example:
8
9
  #
9
10
  # class Conversation < ActiveRecord::Base
10
- # enum status: [ :active, :archived ]
11
+ # enum :status, [ :active, :archived ]
11
12
  # end
12
13
  #
13
14
  # # conversation.update! status: 0
@@ -31,7 +32,9 @@ module ActiveRecord
31
32
  # as well. With the above example:
32
33
  #
33
34
  # Conversation.active
35
+ # Conversation.not_active
34
36
  # Conversation.archived
37
+ # Conversation.not_archived
35
38
  #
36
39
  # Of course, you can also query them directly if the scopes don't fit your
37
40
  # needs:
@@ -39,19 +42,33 @@ module ActiveRecord
39
42
  # Conversation.where(status: [:active, :archived])
40
43
  # Conversation.where.not(status: :active)
41
44
  #
42
- # You can set the default value from the database declaration, like:
45
+ # Defining scopes can be disabled by setting +:scopes+ to +false+.
43
46
  #
44
- # create_table :conversations do |t|
45
- # t.column :status, :integer, default: 0
47
+ # class Conversation < ActiveRecord::Base
48
+ # enum :status, [ :active, :archived ], scopes: false
46
49
  # end
47
50
  #
48
- # Good practice is to let the first declared status be the default.
51
+ # You can set the default enum value by setting +:default+, like:
52
+ #
53
+ # class Conversation < ActiveRecord::Base
54
+ # enum :status, [ :active, :archived ], default: :active
55
+ # end
49
56
  #
50
- # Finally, it's also possible to explicitly map the relation between attribute and
57
+ # conversation = Conversation.new
58
+ # conversation.status # => "active"
59
+ #
60
+ # It's possible to explicitly map the relation between attribute and
51
61
  # database integer with a hash:
52
62
  #
53
63
  # class Conversation < ActiveRecord::Base
54
- # enum status: { active: 0, archived: 1 }
64
+ # enum :status, active: 0, archived: 1
65
+ # end
66
+ #
67
+ # Finally it's also possible to use a string column to persist the enumerated value.
68
+ # Note that this will likely lead to slower database queries:
69
+ #
70
+ # class Conversation < ActiveRecord::Base
71
+ # enum :status, active: "active", archived: "archived"
55
72
  # end
56
73
  #
57
74
  # Note that when an array is used, the implicit mapping from the values to database
@@ -76,14 +93,14 @@ module ActiveRecord
76
93
  #
77
94
  # Conversation.where("status <> ?", Conversation.statuses[:archived])
78
95
  #
79
- # You can use the +:_prefix+ or +:_suffix+ options when you need to define
96
+ # You can use the +:prefix+ or +:suffix+ options when you need to define
80
97
  # multiple enums with same values. If the passed value is +true+, the methods
81
98
  # are prefixed/suffixed with the name of the enum. It is also possible to
82
99
  # supply a custom value:
83
100
  #
84
101
  # class Conversation < ActiveRecord::Base
85
- # enum status: [:active, :archived], _suffix: true
86
- # enum comments_status: [:active, :inactive], _prefix: :comments
102
+ # enum :status, [ :active, :archived ], suffix: true
103
+ # enum :comments_status, [ :active, :inactive ], prefix: :comments
87
104
  # end
88
105
  #
89
106
  # With the above example, the bang and predicate methods along with the
@@ -94,7 +111,6 @@ module ActiveRecord
94
111
  #
95
112
  # conversation.comments_inactive!
96
113
  # conversation.comments_active? # => false
97
-
98
114
  module Enum
99
115
  def self.extended(base) # :nodoc:
100
116
  base.class_attribute(:defined_enums, instance_writer: false, default: {})
@@ -115,24 +131,25 @@ module ActiveRecord
115
131
  end
116
132
 
117
133
  def cast(value)
118
- return if value.blank?
119
-
120
134
  if mapping.has_key?(value)
121
135
  value.to_s
122
136
  elsif mapping.has_value?(value)
123
137
  mapping.key(value)
124
138
  else
125
- assert_valid_value(value)
139
+ value.presence
126
140
  end
127
141
  end
128
142
 
129
143
  def deserialize(value)
130
- return if value.nil?
131
144
  mapping.key(subtype.deserialize(value))
132
145
  end
133
146
 
134
147
  def serialize(value)
135
- mapping.fetch(value, value)
148
+ subtype.serialize(mapping.fetch(value, value))
149
+ end
150
+
151
+ def serializable?(value, &block)
152
+ subtype.serializable?(mapping.fetch(value, value), &block)
136
153
  end
137
154
 
138
155
  def assert_valid_value(value)
@@ -141,83 +158,132 @@ module ActiveRecord
141
158
  end
142
159
  end
143
160
 
144
- # TODO Change this to private once we've dropped Ruby 2.2 support.
145
- # Workaround for Ruby 2.2 "private attribute?" warning.
146
- protected
161
+ attr_reader :subtype
162
+
163
+ private
164
+ attr_reader :name, :mapping
165
+ end
166
+
167
+ def enum(name = nil, values = nil, **options)
168
+ if name
169
+ values, options = options, {} unless values
170
+ return _enum(name, values, **options)
171
+ end
172
+
173
+ definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
174
+ options.transform_keys! { |key| :"#{key[1..-1]}" }
147
175
 
148
- attr_reader :name, :mapping, :subtype
176
+ definitions.each { |name, values| _enum(name, values, **options) }
149
177
  end
150
178
 
151
- def enum(definitions)
152
- klass = self
153
- enum_prefix = definitions.delete(:_prefix)
154
- enum_suffix = definitions.delete(:_suffix)
155
- definitions.each do |name, values|
179
+ private
180
+ def _enum(name, values, prefix: nil, suffix: nil, scopes: true, **options)
181
+ assert_valid_enum_definition_values(values)
156
182
  # statuses = { }
157
183
  enum_values = ActiveSupport::HashWithIndifferentAccess.new
158
184
  name = name.to_s
159
185
 
160
186
  # def self.statuses() statuses end
161
187
  detect_enum_conflict!(name, name.pluralize, true)
162
- singleton_class.send(:define_method, name.pluralize) { enum_values }
188
+ singleton_class.define_method(name.pluralize) { enum_values }
163
189
  defined_enums[name] = enum_values
164
190
 
165
191
  detect_enum_conflict!(name, name)
166
192
  detect_enum_conflict!(name, "#{name}=")
167
193
 
168
- attr = attribute_alias?(name) ? attribute_alias(name) : name
169
- decorate_attribute_type(attr, :enum) do |subtype|
170
- EnumType.new(attr, enum_values, subtype)
194
+ attribute(name, **options) do |subtype|
195
+ subtype = subtype.subtype if EnumType === subtype
196
+ EnumType.new(name, enum_values, subtype)
171
197
  end
172
198
 
199
+ value_method_names = []
173
200
  _enum_methods_module.module_eval do
201
+ prefix = if prefix
202
+ prefix == true ? "#{name}_" : "#{prefix}_"
203
+ end
204
+
205
+ suffix = if suffix
206
+ suffix == true ? "_#{name}" : "_#{suffix}"
207
+ end
208
+
174
209
  pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
175
210
  pairs.each do |label, value|
176
- if enum_prefix == true
177
- prefix = "#{name}_"
178
- elsif enum_prefix
179
- prefix = "#{enum_prefix}_"
180
- end
181
- if enum_suffix == true
182
- suffix = "_#{name}"
183
- elsif enum_suffix
184
- suffix = "_#{enum_suffix}"
185
- end
186
-
187
- value_method_name = "#{prefix}#{label}#{suffix}"
188
211
  enum_values[label] = value
189
212
  label = label.to_s
190
213
 
191
- # def active?() status == "active" end
214
+ value_method_name = "#{prefix}#{label}#{suffix}"
215
+ value_method_names << value_method_name
216
+ define_enum_methods(name, value_method_name, value, scopes)
217
+
218
+ method_friendly_label = label.gsub(/[\W&&[:ascii:]]+/, "_")
219
+ value_method_alias = "#{prefix}#{method_friendly_label}#{suffix}"
220
+
221
+ if value_method_alias != value_method_name && !value_method_names.include?(value_method_alias)
222
+ value_method_names << value_method_alias
223
+ define_enum_methods(name, value_method_alias, value, scopes)
224
+ end
225
+ end
226
+ end
227
+ detect_negative_enum_conditions!(value_method_names) if scopes
228
+ enum_values.freeze
229
+ end
230
+
231
+ class EnumMethods < Module # :nodoc:
232
+ def initialize(klass)
233
+ @klass = klass
234
+ end
235
+
236
+ private
237
+ attr_reader :klass
238
+
239
+ def define_enum_methods(name, value_method_name, value, scopes)
240
+ # def active?() status_for_database == 0 end
192
241
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
193
- define_method("#{value_method_name}?") { self[attr] == label }
242
+ define_method("#{value_method_name}?") { public_send(:"#{name}_for_database") == value }
194
243
 
195
244
  # def active!() update!(status: 0) end
196
245
  klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
197
- define_method("#{value_method_name}!") { update!(attr => value) }
246
+ define_method("#{value_method_name}!") { update!(name => value) }
198
247
 
199
248
  # scope :active, -> { where(status: 0) }
200
- klass.send(:detect_enum_conflict!, name, value_method_name, true)
201
- klass.scope value_method_name, -> { where(attr => value) }
249
+ # scope :not_active, -> { where.not(status: 0) }
250
+ if scopes
251
+ klass.send(:detect_enum_conflict!, name, value_method_name, true)
252
+ klass.scope value_method_name, -> { where(name => value) }
253
+
254
+ klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
255
+ klass.scope "not_#{value_method_name}", -> { where.not(name => value) }
256
+ end
202
257
  end
203
- end
204
- enum_values.freeze
205
258
  end
206
- end
259
+ private_constant :EnumMethods
207
260
 
208
- private
209
261
  def _enum_methods_module
210
262
  @_enum_methods_module ||= begin
211
- mod = Module.new
263
+ mod = EnumMethods.new(self)
212
264
  include mod
213
265
  mod
214
266
  end
215
267
  end
216
268
 
269
+ def assert_valid_enum_definition_values(values)
270
+ unless values.is_a?(Hash) || values.all?(Symbol) || values.all?(String)
271
+ error_message = <<~MSG
272
+ Enum values #{values} must be either a hash, an array of symbols, or an array of strings.
273
+ MSG
274
+ raise ArgumentError, error_message
275
+ end
276
+
277
+ if values.is_a?(Hash) && values.keys.any?(&:blank?) || values.is_a?(Array) && values.any?(&:blank?)
278
+ raise ArgumentError, "Enum label name must not be blank."
279
+ end
280
+ end
281
+
217
282
  ENUM_CONFLICT_MESSAGE = \
218
283
  "You tried to define an enum named \"%{enum}\" on the model \"%{klass}\", but " \
219
284
  "this will generate a %{type} method \"%{method}\", which is already defined " \
220
285
  "by %{source}."
286
+ private_constant :ENUM_CONFLICT_MESSAGE
221
287
 
222
288
  def detect_enum_conflict!(enum_name, method_name, klass_method = false)
223
289
  if klass_method && dangerous_class_method?(method_name)
@@ -240,5 +306,18 @@ module ActiveRecord
240
306
  source: source
241
307
  }
242
308
  end
309
+
310
+ def detect_negative_enum_conditions!(method_names)
311
+ return unless logger
312
+
313
+ method_names.select { |m| m.start_with?("not_") }.each do |potential_not|
314
+ inverted_form = potential_not.sub("not_", "")
315
+ if method_names.include?(inverted_form)
316
+ logger.warn "Enum element '#{potential_not}' in #{self.name} uses the prefix 'not_'." \
317
+ " This has caused a conflict with auto generated negative scopes." \
318
+ " Avoid using enum elements starting with 'not' where the positive form is also an element."
319
+ end
320
+ end
321
+ end
243
322
  end
244
323
  end
@@ -7,6 +7,10 @@ module ActiveRecord
7
7
  class ActiveRecordError < StandardError
8
8
  end
9
9
 
10
+ # Raised when trying to use a feature in Active Record which requires Active Job but the gem is not present.
11
+ class ActiveJobRequiredError < ActiveRecordError
12
+ end
13
+
10
14
  # Raised when the single-table inheritance mechanism fails to locate the subclass
11
15
  # (for example due to improper usage of column that
12
16
  # {ActiveRecord::Base.inheritance_column}[rdoc-ref:ModelSchema::ClassMethods#inheritance_column]
@@ -38,6 +42,10 @@ module ActiveRecord
38
42
  class AdapterNotSpecified < ActiveRecordError
39
43
  end
40
44
 
45
+ # Raised when a model makes a query but it has not specified an associated table.
46
+ class TableNotSpecified < ActiveRecordError
47
+ end
48
+
41
49
  # Raised when Active Record cannot find database adapter specified in
42
50
  # +config/database.yml+ or programmatically.
43
51
  class AdapterNotFound < ActiveRecordError
@@ -49,6 +57,47 @@ module ActiveRecord
49
57
  class ConnectionNotEstablished < ActiveRecordError
50
58
  end
51
59
 
60
+ # Raised when a connection could not be obtained within the connection
61
+ # acquisition timeout period: because max connections in pool
62
+ # are in use.
63
+ class ConnectionTimeoutError < ConnectionNotEstablished
64
+ end
65
+
66
+ # Raised when connection to the database could not been established because it was not
67
+ # able to connect to the host or when the authorization failed.
68
+ class DatabaseConnectionError < ConnectionNotEstablished
69
+ def initialize(message = nil)
70
+ super(message || "Database connection error")
71
+ end
72
+
73
+ class << self
74
+ def hostname_error(hostname)
75
+ DatabaseConnectionError.new(<<~MSG)
76
+ There is an issue connecting with your hostname: #{hostname}.\n
77
+ Please check your database configuration and ensure there is a valid connection to your database.
78
+ MSG
79
+ end
80
+
81
+ def username_error(username)
82
+ DatabaseConnectionError.new(<<~MSG)
83
+ There is an issue connecting to your database with your username/password, username: #{username}.\n
84
+ Please check your database configuration to ensure the username/password are valid.
85
+ MSG
86
+ end
87
+ end
88
+ end
89
+
90
+ # Raised when a pool was unable to get ahold of all its connections
91
+ # to perform a "group" action such as
92
+ # {ActiveRecord::Base.connection_pool.disconnect!}[rdoc-ref:ConnectionAdapters::ConnectionPool#disconnect!]
93
+ # or {ActiveRecord::Base.clear_reloadable_connections!}[rdoc-ref:ConnectionAdapters::ConnectionHandler#clear_reloadable_connections!].
94
+ class ExclusiveConnectionTimeoutError < ConnectionTimeoutError
95
+ end
96
+
97
+ # Raised when a write to the database is attempted on a read only connection.
98
+ class ReadOnlyError < ActiveRecordError
99
+ end
100
+
52
101
  # Raised when Active Record cannot find a record by given id or set of ids.
53
102
  class RecordNotFound < ActiveRecordError
54
103
  attr_reader :model, :primary_key, :id
@@ -64,7 +113,7 @@ module ActiveRecord
64
113
 
65
114
  # Raised by {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] and
66
115
  # {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
67
- # methods when a record is invalid and can not be saved.
116
+ # methods when a record is invalid and cannot be saved.
68
117
  class RecordNotSaved < ActiveRecordError
69
118
  attr_reader :record
70
119
 
@@ -75,7 +124,7 @@ module ActiveRecord
75
124
  end
76
125
 
77
126
  # Raised by {ActiveRecord::Base#destroy!}[rdoc-ref:Persistence#destroy!]
78
- # when a call to {#destroy}[rdoc-ref:Persistence#destroy!]
127
+ # when a call to {#destroy}[rdoc-ref:Persistence#destroy]
79
128
  # would return false.
80
129
  #
81
130
  # begin
@@ -93,13 +142,27 @@ module ActiveRecord
93
142
  end
94
143
  end
95
144
 
145
+ # Raised when Active Record finds multiple records but only expected one.
146
+ class SoleRecordExceeded < ActiveRecordError
147
+ attr_reader :record
148
+
149
+ def initialize(record = nil)
150
+ @record = record
151
+ super "Wanted only one #{record&.name || "record"}"
152
+ end
153
+ end
154
+
96
155
  # Superclass for all database execution errors.
97
156
  #
98
157
  # Wraps the underlying database error as +cause+.
99
158
  class StatementInvalid < ActiveRecordError
100
- def initialize(message = nil)
101
- super(message || $!.try(:message))
159
+ def initialize(message = nil, sql: nil, binds: nil)
160
+ super(message || $!&.message)
161
+ @sql = sql
162
+ @binds = binds
102
163
  end
164
+
165
+ attr_reader :sql, :binds
103
166
  end
104
167
 
105
168
  # Defunct wrapper class kept for compatibility.
@@ -111,14 +174,14 @@ module ActiveRecord
111
174
  class RecordNotUnique < WrappedDatabaseException
112
175
  end
113
176
 
114
- # Raised when a record cannot be inserted or updated because it references a non-existent record.
177
+ # Raised when a record cannot be inserted or updated because it references a non-existent record,
178
+ # or when a record cannot be deleted because a parent record references it.
115
179
  class InvalidForeignKey < WrappedDatabaseException
116
180
  end
117
181
 
118
182
  # Raised when a foreign key constraint cannot be added because the column type does not match the referenced column type.
119
183
  class MismatchedForeignKey < StatementInvalid
120
184
  def initialize(
121
- adapter = nil,
122
185
  message: nil,
123
186
  sql: nil,
124
187
  binds: nil,
@@ -130,14 +193,14 @@ module ActiveRecord
130
193
  )
131
194
  if table
132
195
  type = primary_key_column.bigint? ? :bigint : primary_key_column.type
133
- msg = <<-EOM.squish
196
+ msg = <<~EOM.squish
134
197
  Column `#{foreign_key}` on table `#{table}` does not match column `#{primary_key}` on `#{target_table}`,
135
198
  which has type `#{primary_key_column.sql_type}`.
136
199
  To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :#{type}.
137
200
  (For example `t.#{type} :#{foreign_key}`).
138
201
  EOM
139
202
  else
140
- msg = <<-EOM.squish
203
+ msg = <<~EOM.squish
141
204
  There is a mismatch between the foreign key and primary key column types.
142
205
  Verify that the foreign key column type and the primary key of the associated table match types.
143
206
  EOM
@@ -145,7 +208,7 @@ module ActiveRecord
145
208
  if message
146
209
  msg << "\nOriginal message: #{message}"
147
210
  end
148
- super(msg)
211
+ super(msg, sql: sql, binds: binds)
149
212
  end
150
213
  end
151
214
 
@@ -161,9 +224,9 @@ module ActiveRecord
161
224
  class RangeError < StatementInvalid
162
225
  end
163
226
 
164
- # Raised when number of bind variables in statement given to +:condition+ key
165
- # (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
166
- # does not match number of expected values supplied.
227
+ # Raised when the number of placeholders in an SQL fragment passed to
228
+ # {ActiveRecord::Base.where}[rdoc-ref:QueryMethods#where]
229
+ # does not match the number of values supplied.
167
230
  #
168
231
  # For example, when there are two placeholders with only one value supplied:
169
232
  #
@@ -173,6 +236,34 @@ module ActiveRecord
173
236
 
174
237
  # Raised when a given database does not exist.
175
238
  class NoDatabaseError < StatementInvalid
239
+ include ActiveSupport::ActionableError
240
+
241
+ action "Create database" do
242
+ ActiveRecord::Tasks::DatabaseTasks.create_current
243
+ end
244
+
245
+ def initialize(message = nil)
246
+ super(message || "Database not found")
247
+ end
248
+
249
+ class << self
250
+ def db_error(db_name)
251
+ NoDatabaseError.new(<<~MSG)
252
+ We could not find your database: #{db_name}. Which can be found in the database configuration file located at config/database.yml.
253
+
254
+ To resolve this issue:
255
+
256
+ - Did you create the database for this app, or delete it? You may need to create your database.
257
+ - Has the database name changed? Check your database.yml config has the correct database name.
258
+
259
+ To create your database, run:\n\n bin/rails db:create
260
+ MSG
261
+ end
262
+ end
263
+ end
264
+
265
+ # Raised when creating a database if it exists.
266
+ class DatabaseAlreadyExists < StatementInvalid
176
267
  end
177
268
 
178
269
  # Raised when PostgreSQL returns 'cached plan must not change result type' and
@@ -212,6 +303,10 @@ module ActiveRecord
212
303
  class ReadOnlyRecord < ActiveRecordError
213
304
  end
214
305
 
306
+ # Raised on attempt to lazily load records that are marked as strict loading.
307
+ class StrictLoadingViolationError < ActiveRecordError
308
+ end
309
+
215
310
  # {ActiveRecord::Base.transaction}[rdoc-ref:Transactions::ClassMethods#transaction]
216
311
  # uses this exception to distinguish a deliberate rollback from other exceptional situations.
217
312
  # Normally, raising an exception will cause the
@@ -231,7 +326,7 @@ module ActiveRecord
231
326
  # # The system must fail on Friday so that our support department
232
327
  # # won't be out of job. We silently rollback this transaction
233
328
  # # without telling the user.
234
- # raise ActiveRecord::Rollback, "Call tech support!"
329
+ # raise ActiveRecord::Rollback
235
330
  # end
236
331
  # end
237
332
  # # ActiveRecord::Rollback is the only exception that won't be passed on
@@ -322,10 +417,15 @@ module ActiveRecord
322
417
  # See the following:
323
418
  #
324
419
  # * https://www.postgresql.org/docs/current/static/transaction-iso.html
325
- # * https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html#error_er_lock_deadlock
420
+ # * https://dev.mysql.com/doc/mysql-errors/en/server-error-reference.html#error_er_lock_deadlock
326
421
  class TransactionRollbackError < StatementInvalid
327
422
  end
328
423
 
424
+ # AsynchronousQueryInsideTransactionError will be raised when attempting
425
+ # to perform an asynchronous query from inside a transaction
426
+ class AsynchronousQueryInsideTransactionError < ActiveRecordError
427
+ end
428
+
329
429
  # SerializationFailure will be raised when a transaction is rolled
330
430
  # back by the database due to a serialization failure.
331
431
  class SerializationFailure < TransactionRollbackError
@@ -341,37 +441,43 @@ module ActiveRecord
341
441
  class IrreversibleOrderError < ActiveRecordError
342
442
  end
343
443
 
444
+ # Superclass for errors that have been aborted (either by client or server).
445
+ class QueryAborted < StatementInvalid
446
+ end
447
+
344
448
  # LockWaitTimeout will be raised when lock wait timeout exceeded.
345
449
  class LockWaitTimeout < StatementInvalid
346
450
  end
347
451
 
348
452
  # StatementTimeout will be raised when statement timeout exceeded.
349
- class StatementTimeout < StatementInvalid
453
+ class StatementTimeout < QueryAborted
350
454
  end
351
455
 
352
456
  # QueryCanceled will be raised when canceling statement due to user request.
353
- class QueryCanceled < StatementInvalid
457
+ class QueryCanceled < QueryAborted
458
+ end
459
+
460
+ # AdapterTimeout will be raised when database clients times out while waiting from the server.
461
+ class AdapterTimeout < QueryAborted
354
462
  end
355
463
 
356
464
  # UnknownAttributeReference is raised when an unknown and potentially unsafe
357
- # value is passed to a query method when allow_unsafe_raw_sql is set to
358
- # :disabled. For example, passing a non column name value to a relation's
359
- # #order method might cause this exception.
465
+ # value is passed to a query method. For example, passing a non column name
466
+ # value to a relation's #order method might cause this exception.
360
467
  #
361
468
  # When working around this exception, caution should be taken to avoid SQL
362
469
  # injection vulnerabilities when passing user-provided values to query
363
470
  # methods. Known-safe values can be passed to query methods by wrapping them
364
471
  # in Arel.sql.
365
472
  #
366
- # For example, with allow_unsafe_raw_sql set to :disabled, the following
367
- # code would raise this exception:
473
+ # For example, the following code would raise this exception:
368
474
  #
369
- # Post.order("length(title)").first
475
+ # Post.order("REPLACE(title, 'misc', 'zzzz') asc").pluck(:id)
370
476
  #
371
477
  # The desired result can be accomplished by wrapping the known-safe string
372
478
  # in Arel.sql:
373
479
  #
374
- # Post.order(Arel.sql("length(title)")).first
480
+ # Post.order(Arel.sql("REPLACE(title, 'misc', 'zzzz') asc")).pluck(:id)
375
481
  #
376
482
  # Again, such a workaround should *not* be used when passing user-provided
377
483
  # values, such as request parameters or model attributes to query methods.
@@ -18,7 +18,7 @@ module ActiveRecord
18
18
  # Returns a formatted string ready to be logged.
19
19
  def exec_explain(queries) # :nodoc:
20
20
  str = queries.map do |sql, binds|
21
- msg = "EXPLAIN for: #{sql}".dup
21
+ msg = +"EXPLAIN for: #{sql}"
22
22
  unless binds.empty?
23
23
  msg << " "
24
24
  msg << binds.map { |attr| render_bind(attr) }.inspect
@@ -36,15 +36,19 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  private
39
-
40
39
  def render_bind(attr)
41
- value = if attr.type.binary? && attr.value
42
- "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
40
+ if ActiveModel::Attribute === attr
41
+ value = if attr.type.binary? && attr.value
42
+ "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
43
+ else
44
+ connection.type_cast(attr.value_for_database)
45
+ end
43
46
  else
44
- connection.type_cast(attr.value_for_database)
47
+ value = connection.type_cast(attr)
48
+ attr = nil
45
49
  end
46
50
 
47
- [attr.name, value]
51
+ [attr&.name, value]
48
52
  end
49
53
  end
50
54
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/per_thread_registry"
3
+ require "active_support/core_ext/module/delegation"
4
4
 
5
5
  module ActiveRecord
6
6
  # This is a thread locals registry for EXPLAIN. For example
@@ -8,13 +8,18 @@ module ActiveRecord
8
8
  # ActiveRecord::ExplainRegistry.queries
9
9
  #
10
10
  # returns the collected queries local to the current thread.
11
- #
12
- # See the documentation of ActiveSupport::PerThreadRegistry
13
- # for further details.
14
11
  class ExplainRegistry # :nodoc:
15
- extend ActiveSupport::PerThreadRegistry
12
+ class << self
13
+ delegate :reset, :collect, :collect=, :collect?, :queries, to: :instance
14
+
15
+ private
16
+ def instance
17
+ ActiveSupport::IsolatedExecutionState[:active_record_explain_registry] ||= new
18
+ end
19
+ end
16
20
 
17
- attr_accessor :queries, :collect
21
+ attr_accessor :collect
22
+ attr_reader :queries
18
23
 
19
24
  def initialize
20
25
  reset
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  payload[:exception] ||
27
27
  payload[:cached] ||
28
28
  IGNORED_PAYLOADS.include?(payload[:name]) ||
29
- payload[:sql] !~ EXPLAINED_SQLS
29
+ !payload[:sql].match?(EXPLAINED_SQLS)
30
30
  end
31
31
 
32
32
  ActiveSupport::Notifications.subscribe("sql.active_record", new)