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
@@ -2,11 +2,13 @@
2
2
 
3
3
  module ActiveRecord
4
4
  ###
5
+ # = Active Record \Result
6
+ #
5
7
  # This class encapsulates a result returned from calling
6
8
  # {#exec_query}[rdoc-ref:ConnectionAdapters::DatabaseStatements#exec_query]
7
9
  # on any database connection adapter. For example:
8
10
  #
9
- # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
11
+ # result = ActiveRecord::Base.lease_connection.exec_query('SELECT id, title, body FROM posts')
10
12
  # result # => #<ActiveRecord::Result:0xdeadbeef>
11
13
  #
12
14
  # # Get the column names of the result:
@@ -36,11 +38,22 @@ module ActiveRecord
36
38
 
37
39
  attr_reader :columns, :rows, :column_types
38
40
 
39
- def initialize(columns, rows, column_types = {})
40
- @columns = columns
41
+ def self.empty(async: false) # :nodoc:
42
+ if async
43
+ EMPTY_ASYNC
44
+ else
45
+ EMPTY
46
+ end
47
+ end
48
+
49
+ def initialize(columns, rows, column_types = nil)
50
+ # We freeze the strings to prevent them getting duped when
51
+ # used as keys in ActiveRecord::Base's @attributes hash
52
+ @columns = columns.each(&:-@).freeze
41
53
  @rows = rows
42
54
  @hash_rows = nil
43
- @column_types = column_types
55
+ @column_types = column_types || EMPTY_HASH
56
+ @column_indexes = nil
44
57
  end
45
58
 
46
59
  # Returns true if this result set includes the column named +name+
@@ -57,19 +70,14 @@ module ActiveRecord
57
70
  # row as parameter.
58
71
  #
59
72
  # Returns an +Enumerator+ if no block is given.
60
- def each
73
+ def each(&block)
61
74
  if block_given?
62
- hash_rows.each { |row| yield row }
75
+ hash_rows.each(&block)
63
76
  else
64
77
  hash_rows.to_enum { @rows.size }
65
78
  end
66
79
  end
67
80
 
68
- alias :map! :map
69
- alias :collect! :map
70
- deprecate "map!": :map
71
- deprecate "collect!": :map
72
-
73
81
  # Returns true if there are no records, otherwise false.
74
82
  def empty?
75
83
  rows.empty?
@@ -91,6 +99,14 @@ module ActiveRecord
91
99
  n ? hash_rows.last(n) : hash_rows.last
92
100
  end
93
101
 
102
+ def result # :nodoc:
103
+ self
104
+ end
105
+
106
+ def cancel # :nodoc:
107
+ self
108
+ end
109
+
94
110
  def cast_values(type_overrides = {}) # :nodoc:
95
111
  if columns.one?
96
112
  # Separated to avoid allocating an array per row
@@ -98,7 +114,7 @@ module ActiveRecord
98
114
  type = if type_overrides.is_a?(Array)
99
115
  type_overrides.first
100
116
  else
101
- column_type(columns.first, type_overrides)
117
+ column_type(columns.first, 0, type_overrides)
102
118
  end
103
119
 
104
120
  rows.map do |(value)|
@@ -108,7 +124,7 @@ module ActiveRecord
108
124
  types = if type_overrides.is_a?(Array)
109
125
  type_overrides
110
126
  else
111
- columns.map { |name| column_type(name, type_overrides) }
127
+ columns.map.with_index { |name, i| column_type(name, i, type_overrides) }
112
128
  end
113
129
 
114
130
  rows.map do |values|
@@ -118,58 +134,55 @@ module ActiveRecord
118
134
  end
119
135
 
120
136
  def initialize_copy(other)
121
- @columns = columns.dup
137
+ @columns = columns
122
138
  @rows = rows.dup
123
139
  @column_types = column_types.dup
124
140
  @hash_rows = nil
125
141
  end
126
142
 
143
+ def freeze # :nodoc:
144
+ hash_rows.freeze
145
+ super
146
+ end
147
+
148
+ def column_indexes # :nodoc:
149
+ @column_indexes ||= begin
150
+ index = 0
151
+ hash = {}
152
+ length = columns.length
153
+ while index < length
154
+ hash[columns[index]] = index
155
+ index += 1
156
+ end
157
+ hash
158
+ end
159
+ end
160
+
127
161
  private
128
- def column_type(name, type_overrides = {})
162
+ def column_type(name, index, type_overrides)
129
163
  type_overrides.fetch(name) do
130
- column_types.fetch(name, Type.default_value)
164
+ column_types.fetch(index) do
165
+ column_types.fetch(name, Type.default_value)
166
+ end
131
167
  end
132
168
  end
133
169
 
134
170
  def hash_rows
135
- @hash_rows ||=
136
- begin
137
- # We freeze the strings to prevent them getting duped when
138
- # used as keys in ActiveRecord::Base's @attributes hash
139
- columns = @columns.map(&:-@)
140
- length = columns.length
141
- template = nil
142
-
143
- @rows.map { |row|
144
- if template
145
- # We use transform_values to build subsequent rows from the
146
- # hash of the first row. This is faster because we avoid any
147
- # reallocs and in Ruby 2.7+ avoid hashing entirely.
148
- index = -1
149
- template.transform_values do
150
- row[index += 1]
151
- end
152
- else
153
- # In the past we used Hash[columns.zip(row)]
154
- # though elegant, the verbose way is much more efficient
155
- # both time and memory wise cause it avoids a big array allocation
156
- # this method is called a lot and needs to be micro optimised
157
- hash = {}
158
-
159
- index = 0
160
- while index < length
161
- hash[columns[index]] = row[index]
162
- index += 1
163
- end
164
-
165
- # It's possible to select the same column twice, in which case
166
- # we can't use a template
167
- template = hash if hash.length == length
168
-
169
- hash
170
- end
171
- }
172
- end
171
+ # We use transform_values to rows.
172
+ # This is faster because we avoid any reallocs and avoid hashing entirely.
173
+ @hash_rows ||= @rows.map do |row|
174
+ column_indexes.transform_values { |index| row[index] }
175
+ end
173
176
  end
177
+
178
+ empty_array = [].freeze
179
+ EMPTY_HASH = {}.freeze
180
+ private_constant :EMPTY_HASH
181
+
182
+ EMPTY = new(empty_array, empty_array, EMPTY_HASH).freeze
183
+ private_constant :EMPTY
184
+
185
+ EMPTY_ASYNC = FutureResult.wrap(EMPTY).freeze
186
+ private_constant :EMPTY_ASYNC
174
187
  end
175
188
  end
@@ -1,24 +1,82 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/per_thread_registry"
4
-
5
3
  module ActiveRecord
6
4
  # This is a thread locals registry for Active Record. For example:
7
5
  #
8
- # ActiveRecord::RuntimeRegistry.connection_handler
9
- #
10
- # returns the connection handler local to the current thread.
6
+ # ActiveRecord::RuntimeRegistry.sql_runtime
11
7
  #
12
- # See the documentation of ActiveSupport::PerThreadRegistry
13
- # for further details.
14
- class RuntimeRegistry # :nodoc:
15
- extend ActiveSupport::PerThreadRegistry
8
+ # returns the connection handler local to the current unit of execution (either thread of fiber).
9
+ module RuntimeRegistry # :nodoc:
10
+ extend self
11
+
12
+ def sql_runtime
13
+ ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
14
+ end
15
+
16
+ def sql_runtime=(runtime)
17
+ ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
+ end
19
+
20
+ def async_sql_runtime
21
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
+ end
16
23
 
17
- attr_accessor :sql_runtime
24
+ def async_sql_runtime=(runtime)
25
+ ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
26
+ end
27
+
28
+ def queries_count
29
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
+ end
31
+
32
+ def queries_count=(count)
33
+ ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
+ end
35
+
36
+ def cached_queries_count
37
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
38
+ end
18
39
 
19
- [:sql_runtime].each do |val|
20
- class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
21
- class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
40
+ def cached_queries_count=(count)
41
+ ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
22
42
  end
43
+
44
+ def reset
45
+ reset_runtimes
46
+ reset_queries_count
47
+ reset_cached_queries_count
48
+ end
49
+
50
+ def reset_runtimes
51
+ rt, self.sql_runtime = sql_runtime, 0.0
52
+ self.async_sql_runtime = 0.0
53
+ rt
54
+ end
55
+
56
+ def reset_queries_count
57
+ qc = queries_count
58
+ self.queries_count = 0
59
+ qc
60
+ end
61
+
62
+ def reset_cached_queries_count
63
+ qc = cached_queries_count
64
+ self.cached_queries_count = 0
65
+ qc
66
+ end
67
+ end
68
+ end
69
+
70
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
+ unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
+ ActiveRecord::RuntimeRegistry.queries_count += 1
73
+ ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
+ end
75
+
76
+ runtime = (finish - start) * 1_000.0
77
+
78
+ if payload[:async]
79
+ ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
23
80
  end
81
+ ActiveRecord::RuntimeRegistry.sql_runtime += runtime
24
82
  end
@@ -5,8 +5,8 @@ module ActiveRecord
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- # Accepts an array or string of SQL conditions and sanitizes
9
- # them into a valid SQL fragment for a WHERE clause.
8
+ # Accepts an array of SQL conditions and sanitizes them into a valid
9
+ # SQL fragment for a WHERE clause.
10
10
  #
11
11
  # sanitize_sql_for_conditions(["name=? and group_id=?", "foo'bar", 4])
12
12
  # # => "name='foo''bar' and group_id=4"
@@ -17,8 +17,19 @@ module ActiveRecord
17
17
  # sanitize_sql_for_conditions(["name='%s' and group_id='%s'", "foo'bar", 4])
18
18
  # # => "name='foo''bar' and group_id='4'"
19
19
  #
20
+ # This method will NOT sanitize an SQL string since it won't contain
21
+ # any conditions in it and will return the string as is.
22
+ #
20
23
  # sanitize_sql_for_conditions("name='foo''bar' and group_id='4'")
21
24
  # # => "name='foo''bar' and group_id='4'"
25
+ #
26
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
27
+ # and will directly use the database adapter's +quote+ method.
28
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
29
+ # to prevent query manipulation attacks.
30
+ #
31
+ # sanitize_sql_for_conditions(["role = ?", 0])
32
+ # # => "role = '0'"
22
33
  def sanitize_sql_for_conditions(condition)
23
34
  return nil if condition.blank?
24
35
 
@@ -29,8 +40,8 @@ module ActiveRecord
29
40
  end
30
41
  alias :sanitize_sql :sanitize_sql_for_conditions
31
42
 
32
- # Accepts an array, hash, or string of SQL conditions and sanitizes
33
- # them into a valid SQL fragment for a SET clause.
43
+ # Accepts an array or hash of SQL conditions and sanitizes them into
44
+ # a valid SQL fragment for a SET clause.
34
45
  #
35
46
  # sanitize_sql_for_assignment(["name=? and group_id=?", nil, 4])
36
47
  # # => "name=NULL and group_id=4"
@@ -41,8 +52,19 @@ module ActiveRecord
41
52
  # Post.sanitize_sql_for_assignment({ name: nil, group_id: 4 })
42
53
  # # => "`posts`.`name` = NULL, `posts`.`group_id` = 4"
43
54
  #
55
+ # This method will NOT sanitize an SQL string since it won't contain
56
+ # any conditions in it and will return the string as is.
57
+ #
44
58
  # sanitize_sql_for_assignment("name=NULL and group_id='4'")
45
59
  # # => "name=NULL and group_id='4'"
60
+ #
61
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
62
+ # and will directly use the database adapter's +quote+ method.
63
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
64
+ # to prevent query manipulation attacks.
65
+ #
66
+ # sanitize_sql_for_assignment(["role = ?", 0])
67
+ # # => "role = '0'"
46
68
  def sanitize_sql_for_assignment(assignments, default_table_name = table_name)
47
69
  case assignments
48
70
  when Array; sanitize_sql_array(assignments)
@@ -54,7 +76,7 @@ module ActiveRecord
54
76
  # Accepts an array, or string of SQL conditions and sanitizes
55
77
  # them into a valid SQL fragment for an ORDER clause.
56
78
  #
57
- # sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
79
+ # sanitize_sql_for_order([Arel.sql("field(id, ?)"), [1,3,2]])
58
80
  # # => "field(id, 1,3,2)"
59
81
  #
60
82
  # sanitize_sql_for_order("id ASC")
@@ -63,7 +85,7 @@ module ActiveRecord
63
85
  if condition.is_a?(Array) && condition.first.to_s.include?("?")
64
86
  disallow_raw_sql!(
65
87
  [condition.first],
66
- permit: connection.column_name_with_order_matcher
88
+ permit: adapter_class.column_name_with_order_matcher
67
89
  )
68
90
 
69
91
  # Ensure we aren't dealing with a subclass of String that might
@@ -92,26 +114,32 @@ module ActiveRecord
92
114
  end
93
115
 
94
116
  # Sanitizes a +string+ so that it is safe to use within an SQL
95
- # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%".
117
+ # LIKE statement. This method uses +escape_character+ to escape all
118
+ # occurrences of itself, "_" and "%".
96
119
  #
97
- # sanitize_sql_like("100%")
98
- # # => "100\\%"
120
+ # sanitize_sql_like("100% true!")
121
+ # # => "100\\% true!"
99
122
  #
100
123
  # sanitize_sql_like("snake_cased_string")
101
124
  # # => "snake\\_cased\\_string"
102
125
  #
103
- # sanitize_sql_like("100%", "!")
104
- # # => "100!%"
126
+ # sanitize_sql_like("100% true!", "!")
127
+ # # => "100!% true!!"
105
128
  #
106
129
  # sanitize_sql_like("snake_cased_string", "!")
107
130
  # # => "snake!_cased!_string"
108
131
  def sanitize_sql_like(string, escape_character = "\\")
109
- pattern = Regexp.union(escape_character, "%", "_")
110
- string.gsub(pattern) { |x| [escape_character, x].join }
132
+ if string.include?(escape_character) && escape_character != "%" && escape_character != "_"
133
+ string = string.gsub(escape_character, '\0\0')
134
+ end
135
+
136
+ string.gsub(/(?=[%_])/, escape_character)
111
137
  end
112
138
 
113
139
  # Accepts an array of conditions. The array has each value
114
- # sanitized and interpolated into the SQL statement.
140
+ # sanitized and interpolated into the SQL statement. If using named bind
141
+ # variables in SQL statements where a colon is required verbatim use a
142
+ # backslash to escape.
115
143
  #
116
144
  # sanitize_sql_array(["name=? and group_id=?", "foo'bar", 4])
117
145
  # # => "name='foo''bar' and group_id=4"
@@ -119,77 +147,99 @@ module ActiveRecord
119
147
  # sanitize_sql_array(["name=:name and group_id=:group_id", name: "foo'bar", group_id: 4])
120
148
  # # => "name='foo''bar' and group_id=4"
121
149
  #
150
+ # sanitize_sql_array(["TO_TIMESTAMP(:date, 'YYYY/MM/DD HH12\\:MI\\:SS')", date: "foo"])
151
+ # # => "TO_TIMESTAMP('foo', 'YYYY/MM/DD HH12:MI:SS')"
152
+ #
122
153
  # sanitize_sql_array(["name='%s' and group_id='%s'", "foo'bar", 4])
123
154
  # # => "name='foo''bar' and group_id='4'"
155
+ #
156
+ # Note that this sanitization method is not schema-aware, hence won't do any type casting
157
+ # and will directly use the database adapter's +quote+ method.
158
+ # For MySQL specifically this means that numeric parameters will be quoted as strings
159
+ # to prevent query manipulation attacks.
160
+ #
161
+ # sanitize_sql_array(["role = ?", 0])
162
+ # # => "role = '0'"
124
163
  def sanitize_sql_array(ary)
125
164
  statement, *values = ary
126
165
  if values.first.is_a?(Hash) && /:\w+/.match?(statement)
127
- replace_named_bind_variables(statement, values.first)
166
+ with_connection do |c|
167
+ replace_named_bind_variables(c, statement, values.first)
168
+ end
128
169
  elsif statement.include?("?")
129
- replace_bind_variables(statement, values)
170
+ with_connection do |c|
171
+ replace_bind_variables(c, statement, values)
172
+ end
130
173
  elsif statement.blank?
131
174
  statement
132
175
  else
133
- statement % values.collect { |value| connection.quote_string(value.to_s) }
176
+ with_connection do |c|
177
+ statement % values.collect { |value| c.quote_string(value.to_s) }
178
+ end
134
179
  end
135
180
  end
136
181
 
137
- def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
182
+ def disallow_raw_sql!(args, permit: adapter_class.column_name_matcher) # :nodoc:
138
183
  unexpected = nil
139
184
  args.each do |arg|
140
- next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s)
185
+ next if arg.is_a?(Symbol) || Arel.arel_node?(arg) || permit.match?(arg.to_s.strip)
141
186
  (unexpected ||= []) << arg
142
187
  end
143
188
 
144
189
  if unexpected
145
190
  raise(ActiveRecord::UnknownAttributeReference,
146
- "Query method called with non-attribute argument(s): " +
147
- unexpected.map(&:inspect).join(", ")
191
+ "Dangerous query method (method whose arguments are used as raw " \
192
+ "SQL) called with non-attribute argument(s): " \
193
+ "#{unexpected.map(&:inspect).join(", ")}." \
194
+ "This method should not be called with user-provided values, such as request " \
195
+ "parameters or model attributes. Known-safe values can be passed " \
196
+ "by wrapping them in Arel.sql()."
148
197
  )
149
198
  end
150
199
  end
151
200
 
152
201
  private
153
- def replace_bind_variables(statement, values)
202
+ def replace_bind_variables(connection, statement, values)
154
203
  raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
155
204
  bound = values.dup
156
- c = connection
157
205
  statement.gsub(/\?/) do
158
- replace_bind_variable(bound.shift, c)
206
+ replace_bind_variable(connection, bound.shift)
159
207
  end
160
208
  end
161
209
 
162
- def replace_bind_variable(value, c = connection)
210
+ def replace_bind_variable(connection, value)
163
211
  if ActiveRecord::Relation === value
164
212
  value.to_sql
165
213
  else
166
- quote_bound_value(value, c)
214
+ quote_bound_value(connection, value)
167
215
  end
168
216
  end
169
217
 
170
- def replace_named_bind_variables(statement, bind_vars)
171
- statement.gsub(/(:?):([a-zA-Z]\w*)/) do |match|
172
- if $1 == ":" # skip postgresql casts
218
+ def replace_named_bind_variables(connection, statement, bind_vars)
219
+ statement.gsub(/([:\\]?):([a-zA-Z]\w*)/) do |match|
220
+ if $1 == ":" # skip PostgreSQL casts
173
221
  match # return the whole match
222
+ elsif $1 == "\\" # escaped literal colon
223
+ match[1..-1] # return match with escaping backlash char removed
174
224
  elsif bind_vars.include?(match = $2.to_sym)
175
- replace_bind_variable(bind_vars[match])
225
+ replace_bind_variable(connection, bind_vars[match])
176
226
  else
177
227
  raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
178
228
  end
179
229
  end
180
230
  end
181
231
 
182
- def quote_bound_value(value, c = connection)
232
+ def quote_bound_value(connection, value)
183
233
  if value.respond_to?(:map) && !value.acts_like?(:string)
184
234
  values = value.map { |v| v.respond_to?(:id_for_database) ? v.id_for_database : v }
185
235
  if values.empty?
186
- c.quote(nil)
236
+ connection.quote(connection.cast_bound_value(nil))
187
237
  else
188
- values.map! { |v| c.quote(v) }.join(",")
238
+ values.map! { |v| connection.quote(connection.cast_bound_value(v)) }.join(",")
189
239
  end
190
240
  else
191
241
  value = value.id_for_database if value.respond_to?(:id_for_database)
192
- c.quote(value)
242
+ connection.quote(connection.cast_bound_value(value))
193
243
  end
194
244
  end
195
245
 
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  #
11
11
  # Usage:
12
12
  #
13
- # ActiveRecord::Schema.define do
13
+ # ActiveRecord::Schema[7.0].define do
14
14
  # create_table :authors do |t|
15
15
  # t.string :name, null: false
16
16
  # end
@@ -30,32 +30,48 @@ module ActiveRecord
30
30
  # ActiveRecord::Schema is only supported by database adapters that also
31
31
  # support migrations, the two features being very similar.
32
32
  class Schema < Migration::Current
33
- # Eval the given block. All methods available to the current connection
34
- # adapter are available within the block, so you can easily use the
35
- # database definition DSL to build up your schema (
36
- # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
37
- # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
38
- #
39
- # The +info+ hash is optional, and if given is used to define metadata
40
- # about the current schema (currently, only the schema's version):
41
- #
42
- # ActiveRecord::Schema.define(version: 2038_01_19_000001) do
43
- # ...
44
- # end
45
- def self.define(info = {}, &block)
46
- new.define(info, &block)
47
- end
33
+ module Definition
34
+ extend ActiveSupport::Concern
35
+
36
+ module ClassMethods
37
+ # Eval the given block. All methods available to the current connection
38
+ # adapter are available within the block, so you can easily use the
39
+ # database definition DSL to build up your schema (
40
+ # {create_table}[rdoc-ref:ConnectionAdapters::SchemaStatements#create_table],
41
+ # {add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index], etc.).
42
+ #
43
+ # The +info+ hash is optional, and if given is used to define metadata
44
+ # about the current schema (currently, only the schema's version):
45
+ #
46
+ # ActiveRecord::Schema[7.0].define(version: 2038_01_19_000001) do
47
+ # ...
48
+ # end
49
+ def define(info = {}, &block)
50
+ new.define(info, &block)
51
+ end
52
+ end
53
+
54
+ def define(info, &block) # :nodoc:
55
+ connection_pool.with_connection do |connection|
56
+ instance_eval(&block)
48
57
 
49
- def define(info, &block) # :nodoc:
50
- instance_eval(&block)
58
+ connection_pool.schema_migration.create_table
59
+ if info[:version].present?
60
+ connection.assume_migrated_upto_version(info[:version])
61
+ end
51
62
 
52
- if info[:version].present?
53
- connection.schema_migration.create_table
54
- connection.assume_migrated_upto_version(info[:version])
63
+ connection_pool.internal_metadata.create_table_and_set_flags(connection_pool.migration_context.current_environment)
64
+ end
55
65
  end
66
+ end
67
+
68
+ include Definition
56
69
 
57
- ActiveRecord::InternalMetadata.create_table
58
- ActiveRecord::InternalMetadata[:environment] = connection.migration_context.current_environment
70
+ def self.[](version)
71
+ @class_for_version ||= {}
72
+ @class_for_version[version] ||= Class.new(Migration::Compatibility.find(version)) do
73
+ include Definition
74
+ end
59
75
  end
60
76
  end
61
77
  end