activerecord 6.1.7 → 7.1.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 (307) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1516 -1019
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +17 -14
  6. data/lib/active_record/association_relation.rb +1 -11
  7. data/lib/active_record/associations/association.rb +50 -19
  8. data/lib/active_record/associations/association_scope.rb +17 -12
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -9
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
  11. data/lib/active_record/associations/builder/association.rb +11 -5
  12. data/lib/active_record/associations/builder/belongs_to.rb +40 -14
  13. data/lib/active_record/associations/builder/collection_association.rb +10 -3
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  15. data/lib/active_record/associations/builder/has_many.rb +3 -2
  16. data/lib/active_record/associations/builder/has_one.rb +2 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +6 -2
  18. data/lib/active_record/associations/collection_association.rb +35 -31
  19. data/lib/active_record/associations/collection_proxy.rb +30 -15
  20. data/lib/active_record/associations/disable_joins_association_scope.rb +59 -0
  21. data/lib/active_record/associations/foreign_association.rb +10 -3
  22. data/lib/active_record/associations/has_many_association.rb +28 -18
  23. data/lib/active_record/associations/has_many_through_association.rb +12 -7
  24. data/lib/active_record/associations/has_one_association.rb +20 -10
  25. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  26. data/lib/active_record/associations/join_dependency.rb +26 -16
  27. data/lib/active_record/associations/preloader/association.rb +207 -52
  28. data/lib/active_record/associations/preloader/batch.rb +48 -0
  29. data/lib/active_record/associations/preloader/branch.rb +147 -0
  30. data/lib/active_record/associations/preloader/through_association.rb +50 -14
  31. data/lib/active_record/associations/preloader.rb +50 -121
  32. data/lib/active_record/associations/singular_association.rb +9 -3
  33. data/lib/active_record/associations/through_association.rb +25 -14
  34. data/lib/active_record/associations.rb +423 -289
  35. data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
  36. data/lib/active_record/attribute_assignment.rb +1 -3
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
  38. data/lib/active_record/attribute_methods/dirty.rb +61 -14
  39. data/lib/active_record/attribute_methods/primary_key.rb +78 -26
  40. data/lib/active_record/attribute_methods/query.rb +31 -19
  41. data/lib/active_record/attribute_methods/read.rb +25 -10
  42. data/lib/active_record/attribute_methods/serialization.rb +194 -37
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
  44. data/lib/active_record/attribute_methods/write.rb +10 -13
  45. data/lib/active_record/attribute_methods.rb +121 -40
  46. data/lib/active_record/attributes.rb +27 -38
  47. data/lib/active_record/autosave_association.rb +61 -30
  48. data/lib/active_record/base.rb +25 -2
  49. data/lib/active_record/callbacks.rb +18 -34
  50. data/lib/active_record/coders/column_serializer.rb +61 -0
  51. data/lib/active_record/coders/json.rb +1 -1
  52. data/lib/active_record/coders/yaml_column.rb +70 -46
  53. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +367 -0
  54. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +171 -51
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -136
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +622 -149
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
  69. data/lib/active_record/connection_adapters/column.rb +13 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +104 -53
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -11
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +18 -1
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -52
  83. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +381 -69
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +492 -230
  100. data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +65 -53
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
  104. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
  107. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  108. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  109. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  110. data/lib/active_record/connection_adapters.rb +9 -6
  111. data/lib/active_record/connection_handling.rb +107 -136
  112. data/lib/active_record/core.rb +194 -224
  113. data/lib/active_record/counter_cache.rb +46 -25
  114. data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
  115. data/lib/active_record/database_configurations/database_config.rb +21 -12
  116. data/lib/active_record/database_configurations/hash_config.rb +84 -16
  117. data/lib/active_record/database_configurations/url_config.rb +18 -12
  118. data/lib/active_record/database_configurations.rb +95 -59
  119. data/lib/active_record/delegated_type.rb +61 -15
  120. data/lib/active_record/deprecator.rb +7 -0
  121. data/lib/active_record/destroy_association_async_job.rb +3 -1
  122. data/lib/active_record/disable_joins_association_relation.rb +39 -0
  123. data/lib/active_record/dynamic_matchers.rb +1 -1
  124. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  125. data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
  126. data/lib/active_record/encryption/cipher.rb +53 -0
  127. data/lib/active_record/encryption/config.rb +68 -0
  128. data/lib/active_record/encryption/configurable.rb +60 -0
  129. data/lib/active_record/encryption/context.rb +42 -0
  130. data/lib/active_record/encryption/contexts.rb +76 -0
  131. data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
  132. data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
  133. data/lib/active_record/encryption/encryptable_record.rb +224 -0
  134. data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
  135. data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
  136. data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
  137. data/lib/active_record/encryption/encryptor.rb +155 -0
  138. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
  139. data/lib/active_record/encryption/errors.rb +15 -0
  140. data/lib/active_record/encryption/extended_deterministic_queries.rb +172 -0
  141. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
  142. data/lib/active_record/encryption/key.rb +28 -0
  143. data/lib/active_record/encryption/key_generator.rb +53 -0
  144. data/lib/active_record/encryption/key_provider.rb +46 -0
  145. data/lib/active_record/encryption/message.rb +33 -0
  146. data/lib/active_record/encryption/message_serializer.rb +92 -0
  147. data/lib/active_record/encryption/null_encryptor.rb +21 -0
  148. data/lib/active_record/encryption/properties.rb +76 -0
  149. data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
  150. data/lib/active_record/encryption/scheme.rb +96 -0
  151. data/lib/active_record/encryption.rb +56 -0
  152. data/lib/active_record/enum.rb +156 -62
  153. data/lib/active_record/errors.rb +171 -15
  154. data/lib/active_record/explain.rb +23 -3
  155. data/lib/active_record/explain_registry.rb +11 -6
  156. data/lib/active_record/explain_subscriber.rb +1 -1
  157. data/lib/active_record/fixture_set/file.rb +15 -1
  158. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  159. data/lib/active_record/fixture_set/render_context.rb +2 -0
  160. data/lib/active_record/fixture_set/table_row.rb +70 -14
  161. data/lib/active_record/fixture_set/table_rows.rb +4 -4
  162. data/lib/active_record/fixtures.rb +131 -86
  163. data/lib/active_record/future_result.rb +164 -0
  164. data/lib/active_record/gem_version.rb +3 -3
  165. data/lib/active_record/inheritance.rb +81 -29
  166. data/lib/active_record/insert_all.rb +133 -20
  167. data/lib/active_record/integration.rb +11 -10
  168. data/lib/active_record/internal_metadata.rb +117 -33
  169. data/lib/active_record/legacy_yaml_adapter.rb +2 -39
  170. data/lib/active_record/locking/optimistic.rb +36 -21
  171. data/lib/active_record/locking/pessimistic.rb +15 -6
  172. data/lib/active_record/log_subscriber.rb +52 -19
  173. data/lib/active_record/marshalling.rb +56 -0
  174. data/lib/active_record/message_pack.rb +124 -0
  175. data/lib/active_record/middleware/database_selector/resolver.rb +10 -10
  176. data/lib/active_record/middleware/database_selector.rb +23 -13
  177. data/lib/active_record/middleware/shard_selector.rb +62 -0
  178. data/lib/active_record/migration/command_recorder.rb +108 -13
  179. data/lib/active_record/migration/compatibility.rb +221 -48
  180. data/lib/active_record/migration/default_strategy.rb +23 -0
  181. data/lib/active_record/migration/execution_strategy.rb +19 -0
  182. data/lib/active_record/migration/join_table.rb +1 -1
  183. data/lib/active_record/migration.rb +355 -171
  184. data/lib/active_record/model_schema.rb +116 -97
  185. data/lib/active_record/nested_attributes.rb +36 -15
  186. data/lib/active_record/no_touching.rb +3 -3
  187. data/lib/active_record/normalization.rb +159 -0
  188. data/lib/active_record/persistence.rb +405 -85
  189. data/lib/active_record/promise.rb +84 -0
  190. data/lib/active_record/query_cache.rb +3 -21
  191. data/lib/active_record/query_logs.rb +174 -0
  192. data/lib/active_record/query_logs_formatter.rb +41 -0
  193. data/lib/active_record/querying.rb +29 -6
  194. data/lib/active_record/railtie.rb +219 -43
  195. data/lib/active_record/railties/controller_runtime.rb +13 -9
  196. data/lib/active_record/railties/databases.rake +185 -249
  197. data/lib/active_record/railties/job_runtime.rb +23 -0
  198. data/lib/active_record/readonly_attributes.rb +41 -3
  199. data/lib/active_record/reflection.rb +229 -80
  200. data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
  201. data/lib/active_record/relation/batches.rb +192 -63
  202. data/lib/active_record/relation/calculations.rb +211 -90
  203. data/lib/active_record/relation/delegation.rb +27 -13
  204. data/lib/active_record/relation/finder_methods.rb +108 -51
  205. data/lib/active_record/relation/merger.rb +22 -13
  206. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  207. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  208. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  209. data/lib/active_record/relation/predicate_builder.rb +27 -20
  210. data/lib/active_record/relation/query_attribute.rb +30 -12
  211. data/lib/active_record/relation/query_methods.rb +654 -127
  212. data/lib/active_record/relation/record_fetch_warning.rb +7 -9
  213. data/lib/active_record/relation/spawn_methods.rb +20 -3
  214. data/lib/active_record/relation/where_clause.rb +10 -19
  215. data/lib/active_record/relation.rb +262 -120
  216. data/lib/active_record/result.rb +37 -11
  217. data/lib/active_record/runtime_registry.rb +18 -13
  218. data/lib/active_record/sanitization.rb +65 -20
  219. data/lib/active_record/schema.rb +36 -22
  220. data/lib/active_record/schema_dumper.rb +73 -24
  221. data/lib/active_record/schema_migration.rb +68 -33
  222. data/lib/active_record/scoping/default.rb +72 -15
  223. data/lib/active_record/scoping/named.rb +5 -13
  224. data/lib/active_record/scoping.rb +65 -34
  225. data/lib/active_record/secure_password.rb +60 -0
  226. data/lib/active_record/secure_token.rb +21 -3
  227. data/lib/active_record/serialization.rb +6 -1
  228. data/lib/active_record/signed_id.rb +10 -8
  229. data/lib/active_record/store.rb +10 -10
  230. data/lib/active_record/suppressor.rb +13 -15
  231. data/lib/active_record/table_metadata.rb +16 -3
  232. data/lib/active_record/tasks/database_tasks.rb +225 -136
  233. data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
  234. data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
  235. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  236. data/lib/active_record/test_databases.rb +1 -1
  237. data/lib/active_record/test_fixtures.rb +116 -96
  238. data/lib/active_record/timestamp.rb +28 -17
  239. data/lib/active_record/token_for.rb +113 -0
  240. data/lib/active_record/touch_later.rb +11 -6
  241. data/lib/active_record/transactions.rb +48 -27
  242. data/lib/active_record/translation.rb +3 -3
  243. data/lib/active_record/type/adapter_specific_registry.rb +32 -14
  244. data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
  245. data/lib/active_record/type/internal/timezone.rb +7 -2
  246. data/lib/active_record/type/serialized.rb +9 -5
  247. data/lib/active_record/type/time.rb +4 -0
  248. data/lib/active_record/type/type_map.rb +17 -20
  249. data/lib/active_record/type.rb +1 -2
  250. data/lib/active_record/validations/absence.rb +1 -1
  251. data/lib/active_record/validations/associated.rb +4 -4
  252. data/lib/active_record/validations/numericality.rb +5 -4
  253. data/lib/active_record/validations/presence.rb +5 -28
  254. data/lib/active_record/validations/uniqueness.rb +51 -6
  255. data/lib/active_record/validations.rb +8 -4
  256. data/lib/active_record/version.rb +1 -1
  257. data/lib/active_record.rb +335 -32
  258. data/lib/arel/attributes/attribute.rb +0 -8
  259. data/lib/arel/crud.rb +28 -22
  260. data/lib/arel/delete_manager.rb +18 -4
  261. data/lib/arel/errors.rb +10 -0
  262. data/lib/arel/factory_methods.rb +4 -0
  263. data/lib/arel/filter_predications.rb +9 -0
  264. data/lib/arel/insert_manager.rb +2 -3
  265. data/lib/arel/nodes/and.rb +4 -0
  266. data/lib/arel/nodes/binary.rb +6 -1
  267. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  268. data/lib/arel/nodes/casted.rb +1 -1
  269. data/lib/arel/nodes/cte.rb +36 -0
  270. data/lib/arel/nodes/delete_statement.rb +12 -13
  271. data/lib/arel/nodes/filter.rb +10 -0
  272. data/lib/arel/nodes/fragments.rb +35 -0
  273. data/lib/arel/nodes/function.rb +1 -0
  274. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  275. data/lib/arel/nodes/insert_statement.rb +2 -2
  276. data/lib/arel/nodes/leading_join.rb +8 -0
  277. data/lib/arel/nodes/node.rb +111 -2
  278. data/lib/arel/nodes/select_core.rb +2 -2
  279. data/lib/arel/nodes/select_statement.rb +2 -2
  280. data/lib/arel/nodes/sql_literal.rb +6 -0
  281. data/lib/arel/nodes/table_alias.rb +4 -0
  282. data/lib/arel/nodes/update_statement.rb +8 -3
  283. data/lib/arel/nodes.rb +5 -0
  284. data/lib/arel/predications.rb +13 -3
  285. data/lib/arel/select_manager.rb +10 -4
  286. data/lib/arel/table.rb +9 -6
  287. data/lib/arel/tree_manager.rb +0 -12
  288. data/lib/arel/update_manager.rb +18 -4
  289. data/lib/arel/visitors/dot.rb +80 -90
  290. data/lib/arel/visitors/mysql.rb +16 -3
  291. data/lib/arel/visitors/postgresql.rb +0 -10
  292. data/lib/arel/visitors/to_sql.rb +139 -19
  293. data/lib/arel/visitors/visitor.rb +2 -2
  294. data/lib/arel.rb +18 -3
  295. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  296. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -1
  297. data/lib/rails/generators/active_record/migration.rb +3 -1
  298. data/lib/rails/generators/active_record/model/USAGE +113 -0
  299. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  300. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
  301. data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
  302. data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
  303. data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
  304. data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
  305. metadata +92 -13
  306. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  307. data/lib/active_record/null_relation.rb +0 -67
@@ -1,62 +1,80 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
- require "active_record/connection_adapters/mysql/database_statements"
4
+ require "active_record/connection_adapters/mysql2/database_statements"
5
5
 
6
6
  gem "mysql2", "~> 0.5"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
10
  module ConnectionHandling # :nodoc:
11
+ def mysql2_adapter_class
12
+ ConnectionAdapters::Mysql2Adapter
13
+ end
14
+
11
15
  # Establishes a connection to the database that's used by all Active Record objects.
12
16
  def mysql2_connection(config)
13
- config = config.symbolize_keys
14
- config[:flags] ||= 0
15
-
16
- if config[:flags].kind_of? Array
17
- config[:flags].push "FOUND_ROWS"
18
- else
19
- config[:flags] |= Mysql2::Client::FOUND_ROWS
20
- end
21
-
22
- ConnectionAdapters::Mysql2Adapter.new(
23
- ConnectionAdapters::Mysql2Adapter.new_client(config),
24
- logger,
25
- nil,
26
- config,
27
- )
17
+ mysql2_adapter_class.new(config)
28
18
  end
29
19
  end
30
20
 
31
21
  module ConnectionAdapters
22
+ # = Active Record MySQL2 Adapter
32
23
  class Mysql2Adapter < AbstractMysqlAdapter
33
- ER_BAD_DB_ERROR = 1049
24
+ ER_BAD_DB_ERROR = 1049
25
+ ER_DBACCESS_DENIED_ERROR = 1044
26
+ ER_ACCESS_DENIED_ERROR = 1045
27
+ ER_CONN_HOST_ERROR = 2003
28
+ ER_UNKNOWN_HOST_ERROR = 2005
29
+
34
30
  ADAPTER_NAME = "Mysql2"
35
31
 
36
- include MySQL::DatabaseStatements
32
+ include Mysql2::DatabaseStatements
37
33
 
38
34
  class << self
39
35
  def new_client(config)
40
- Mysql2::Client.new(config)
41
- rescue Mysql2::Error => error
42
- if error.error_number == ConnectionAdapters::Mysql2Adapter::ER_BAD_DB_ERROR
43
- raise ActiveRecord::NoDatabaseError
36
+ ::Mysql2::Client.new(config)
37
+ rescue ::Mysql2::Error => error
38
+ case error.error_number
39
+ when ER_BAD_DB_ERROR
40
+ raise ActiveRecord::NoDatabaseError.db_error(config[:database])
41
+ when ER_DBACCESS_DENIED_ERROR, ER_ACCESS_DENIED_ERROR
42
+ raise ActiveRecord::DatabaseConnectionError.username_error(config[:username])
43
+ when ER_CONN_HOST_ERROR, ER_UNKNOWN_HOST_ERROR
44
+ raise ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
44
45
  else
45
46
  raise ActiveRecord::ConnectionNotEstablished, error.message
46
47
  end
47
48
  end
48
- end
49
49
 
50
- def initialize(connection, logger, connection_options, config)
51
- superclass_config = config.reverse_merge(prepared_statements: false)
52
- super(connection, logger, connection_options, superclass_config)
53
- configure_connection
50
+ private
51
+ def initialize_type_map(m)
52
+ super
53
+
54
+ m.register_type(%r(char)i) do |sql_type|
55
+ limit = extract_limit(sql_type)
56
+ Type.lookup(:string, adapter: :mysql2, limit: limit)
57
+ end
58
+
59
+ m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
60
+ m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
61
+ end
54
62
  end
55
63
 
56
- def self.database_exists?(config)
57
- !!ActiveRecord::Base.mysql2_connection(config)
58
- rescue ActiveRecord::NoDatabaseError
59
- false
64
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
65
+
66
+ def initialize(...)
67
+ super
68
+
69
+ @config[:flags] ||= 0
70
+
71
+ if @config[:flags].kind_of? Array
72
+ @config[:flags].push "FOUND_ROWS"
73
+ else
74
+ @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
75
+ end
76
+
77
+ @connection_parameters ||= @config
60
78
  end
61
79
 
62
80
  def supports_json?
@@ -75,17 +93,19 @@ module ActiveRecord
75
93
  true
76
94
  end
77
95
 
96
+ def savepoint_errors_invalidate_transactions?
97
+ true
98
+ end
99
+
78
100
  def supports_lazy_transactions?
79
101
  true
80
102
  end
81
103
 
82
104
  # HELPER METHODS ===========================================
83
105
 
84
- def each_hash(result) # :nodoc:
106
+ def each_hash(result, &block) # :nodoc:
85
107
  if block_given?
86
- result.each(as: :hash, symbolize_keys: true) do |row|
87
- yield row
88
- end
108
+ result.each(as: :hash, symbolize_keys: true, &block)
89
109
  else
90
110
  to_enum(:each_hash, result)
91
111
  end
@@ -99,10 +119,11 @@ module ActiveRecord
99
119
  # QUOTING ==================================================
100
120
  #++
101
121
 
122
+ # Quotes strings for use in SQL input.
102
123
  def quote_string(string)
103
- @connection.escape(string)
104
- rescue Mysql2::Error => error
105
- raise translate_exception(error, message: error.message, sql: "<escape>", binds: [])
124
+ with_raw_connection(allow_retry: true, materialize_transactions: false) do |connection|
125
+ connection.escape(string)
126
+ end
106
127
  end
107
128
 
108
129
  #--
@@ -110,37 +131,45 @@ module ActiveRecord
110
131
  #++
111
132
 
112
133
  def active?
113
- @connection.ping
134
+ !!@raw_connection&.ping
114
135
  end
115
136
 
116
- def reconnect!
117
- super
118
- disconnect!
119
- connect
120
- end
121
137
  alias :reset! :reconnect!
122
138
 
123
139
  # Disconnects from the database if already connected.
124
140
  # Otherwise, this method does nothing.
125
141
  def disconnect!
126
142
  super
127
- @connection.close
143
+ @raw_connection&.close
144
+ @raw_connection = nil
128
145
  end
129
146
 
130
147
  def discard! # :nodoc:
131
148
  super
132
- @connection.automatic_close = false
133
- @connection = nil
149
+ @raw_connection&.automatic_close = false
150
+ @raw_connection = nil
134
151
  end
135
152
 
136
153
  private
154
+ def text_type?(type)
155
+ TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
156
+ end
157
+
137
158
  def connect
138
- @connection = self.class.new_client(@config)
139
- configure_connection
159
+ @raw_connection = self.class.new_client(@connection_parameters)
160
+ rescue ConnectionNotEstablished => ex
161
+ raise ex.set_pool(@pool)
162
+ end
163
+
164
+ def reconnect
165
+ @raw_connection&.close
166
+ @raw_connection = nil
167
+ connect
140
168
  end
141
169
 
142
170
  def configure_connection
143
- @connection.query_options[:as] = :array
171
+ @raw_connection.query_options[:as] = :array
172
+ @raw_connection.query_options[:database_timezone] = default_timezone
144
173
  super
145
174
  end
146
175
 
@@ -149,16 +178,38 @@ module ActiveRecord
149
178
  end
150
179
 
151
180
  def get_full_version
152
- @connection.server_info[:version]
181
+ any_raw_connection.server_info[:version]
153
182
  end
154
183
 
155
184
  def translate_exception(exception, message:, sql:, binds:)
156
- if exception.is_a?(Mysql2::Error::TimeoutError) && !exception.error_number
157
- ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
185
+ if exception.is_a?(::Mysql2::Error::TimeoutError) && !exception.error_number
186
+ ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
187
+ elsif exception.is_a?(::Mysql2::Error::ConnectionError)
188
+ if exception.message.match?(/MySQL client is not connected/i)
189
+ ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
190
+ else
191
+ ActiveRecord::ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
192
+ end
158
193
  else
159
194
  super
160
195
  end
161
196
  end
197
+
198
+ def default_prepared_statements
199
+ false
200
+ end
201
+
202
+ ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
203
+ Type::ImmutableString.new(true: "1", false: "0", **args)
204
+ end
205
+
206
+ ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
207
+ Type::String.new(true: "1", false: "0", **args)
208
+ end
209
+
210
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
162
211
  end
212
+
213
+ ActiveSupport.run_load_hooks(:active_record_mysql2adapter, Mysql2Adapter)
163
214
  end
164
215
  end
@@ -5,8 +5,13 @@ module ActiveRecord
5
5
  class PoolConfig # :nodoc:
6
6
  include Mutex_m
7
7
 
8
- attr_reader :db_config, :connection_klass
9
- attr_accessor :schema_cache
8
+ attr_reader :db_config, :role, :shard
9
+ attr_writer :schema_reflection
10
+ attr_accessor :connection_class
11
+
12
+ def schema_reflection
13
+ @schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
14
+ end
10
15
 
11
16
  INSTANCES = ObjectSpace::WeakMap.new
12
17
  private_constant :INSTANCES
@@ -15,27 +20,31 @@ module ActiveRecord
15
20
  def discard_pools!
16
21
  INSTANCES.each_key(&:discard_pool!)
17
22
  end
23
+
24
+ def disconnect_all!
25
+ INSTANCES.each_key { |c| c.disconnect!(automatic_reconnect: true) }
26
+ end
18
27
  end
19
28
 
20
- def initialize(connection_klass, db_config)
29
+ def initialize(connection_class, db_config, role, shard)
21
30
  super()
22
- @connection_klass = connection_klass
31
+ @connection_class = connection_class
23
32
  @db_config = db_config
33
+ @role = role
34
+ @shard = shard
24
35
  @pool = nil
25
36
  INSTANCES[self] = self
26
37
  end
27
38
 
28
- def connection_specification_name
29
- if connection_klass.is_a?(String)
30
- connection_klass
31
- elsif connection_klass.primary_class?
39
+ def connection_name
40
+ if connection_class.primary_class?
32
41
  "ActiveRecord::Base"
33
42
  else
34
- connection_klass.name
43
+ connection_class.name
35
44
  end
36
45
  end
37
46
 
38
- def disconnect!
47
+ def disconnect!(automatic_reconnect: false)
39
48
  ActiveSupport::ForkTracker.check!
40
49
 
41
50
  return unless @pool
@@ -43,7 +52,7 @@ module ActiveRecord
43
52
  synchronize do
44
53
  return unless @pool
45
54
 
46
- @pool.automatic_reconnect = false
55
+ @pool.automatic_reconnect = automatic_reconnect
47
56
  @pool.disconnect!
48
57
  end
49
58
 
@@ -4,40 +4,50 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  class PoolManager # :nodoc:
6
6
  def initialize
7
- @name_to_role_mapping = Hash.new { |h, k| h[k] = {} }
7
+ @role_to_shard_mapping = Hash.new { |h, k| h[k] = {} }
8
8
  end
9
9
 
10
10
  def shard_names
11
- @name_to_role_mapping.values.flat_map { |shard_map| shard_map.keys }
11
+ @role_to_shard_mapping.values.flat_map { |shard_map| shard_map.keys }.uniq
12
12
  end
13
13
 
14
14
  def role_names
15
- @name_to_role_mapping.keys
15
+ @role_to_shard_mapping.keys
16
16
  end
17
17
 
18
18
  def pool_configs(role = nil)
19
19
  if role
20
- @name_to_role_mapping[role].values
20
+ @role_to_shard_mapping[role].values
21
21
  else
22
- @name_to_role_mapping.flat_map { |_, shard_map| shard_map.values }
22
+ @role_to_shard_mapping.flat_map { |_, shard_map| shard_map.values }
23
+ end
24
+ end
25
+
26
+ def each_pool_config(role = nil, &block)
27
+ if role
28
+ @role_to_shard_mapping[role].each_value(&block)
29
+ else
30
+ @role_to_shard_mapping.each_value do |shard_map|
31
+ shard_map.each_value(&block)
32
+ end
23
33
  end
24
34
  end
25
35
 
26
36
  def remove_role(role)
27
- @name_to_role_mapping.delete(role)
37
+ @role_to_shard_mapping.delete(role)
28
38
  end
29
39
 
30
40
  def remove_pool_config(role, shard)
31
- @name_to_role_mapping[role].delete(shard)
41
+ @role_to_shard_mapping[role].delete(shard)
32
42
  end
33
43
 
34
44
  def get_pool_config(role, shard)
35
- @name_to_role_mapping[role][shard]
45
+ @role_to_shard_mapping[role][shard]
36
46
  end
37
47
 
38
48
  def set_pool_config(role, shard, pool_config)
39
49
  if pool_config
40
- @name_to_role_mapping[role][shard] = pool_config
50
+ @role_to_shard_mapping[role][shard] = pool_config
41
51
  else
42
52
  raise ArgumentError, "The `pool_config` for the :#{role} role and :#{shard} shard was `nil`. Please check your configuration. If you want your writing role to be something other than `:writing` set `config.active_record.writing_role` in your application configuration. The same setting should be applied for the `reading_role` if applicable."
43
53
  end
@@ -6,31 +6,48 @@ module ActiveRecord
6
6
  class Column < ConnectionAdapters::Column # :nodoc:
7
7
  delegate :oid, :fmod, to: :sql_type_metadata
8
8
 
9
- def initialize(*, serial: nil, **)
9
+ def initialize(*, serial: nil, generated: nil, **)
10
10
  super
11
11
  @serial = serial
12
+ @generated = generated
12
13
  end
13
14
 
14
15
  def serial?
15
16
  @serial
16
17
  end
18
+ alias_method :auto_incremented_by_db?, :serial?
19
+
20
+ def virtual?
21
+ # We assume every generated column is virtual, no matter the concrete type
22
+ @generated.present?
23
+ end
24
+
25
+ def has_default?
26
+ super && !virtual?
27
+ end
17
28
 
18
29
  def array
19
30
  sql_type_metadata.sql_type.end_with?("[]")
20
31
  end
21
32
  alias :array? :array
22
33
 
34
+ def enum?
35
+ type == :enum
36
+ end
37
+
23
38
  def sql_type
24
39
  super.delete_suffix("[]")
25
40
  end
26
41
 
27
42
  def init_with(coder)
28
43
  @serial = coder["serial"]
44
+ @generated = coder["generated"]
29
45
  super
30
46
  end
31
47
 
32
48
  def encode_with(coder)
33
49
  coder["serial"] = @serial
50
+ coder["generated"] = @generated
34
51
  super
35
52
  end
36
53
 
@@ -4,19 +4,19 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module PostgreSQL
6
6
  module DatabaseStatements
7
- def explain(arel, binds = [])
8
- sql = "EXPLAIN #{to_sql(arel, binds)}"
9
- PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds))
7
+ def explain(arel, binds = [], options = [])
8
+ sql = build_explain_clause(options) + " " + to_sql(arel, binds)
9
+ result = internal_exec_query(sql, "EXPLAIN", binds)
10
+ PostgreSQL::ExplainPrettyPrinter.new.pp(result)
10
11
  end
11
12
 
12
13
  # Queries the database and returns the results in an Array-like object
13
- def query(sql, name = nil) #:nodoc:
14
- materialize_transactions
14
+ def query(sql, name = nil) # :nodoc:
15
15
  mark_transaction_written_if_write(sql)
16
16
 
17
17
  log(sql, name) do
18
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
19
- @connection.async_exec(sql).map_types!(@type_map_for_results).values
18
+ with_raw_connection do |conn|
19
+ conn.async_exec(sql).map_types!(@type_map_for_results).values
20
20
  end
21
21
  end
22
22
  end
@@ -34,65 +34,52 @@ module ActiveRecord
34
34
 
35
35
  # Executes an SQL statement, returning a PG::Result object on success
36
36
  # or raising a PG::Error exception otherwise.
37
+ #
38
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
39
+ # executing the SQL statement in case of a connection-related exception.
40
+ # This option should only be enabled for known idempotent queries.
41
+ #
37
42
  # Note: the PG::Result object is manually memory managed; if you don't
38
43
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
39
- def execute(sql, name = nil)
40
- if preventing_writes? && write_query?(sql)
41
- raise ActiveRecord::ReadOnlyError, "Write query attempted while in readonly mode: #{sql}"
42
- end
43
-
44
- materialize_transactions
45
- mark_transaction_written_if_write(sql)
44
+ def execute(...) # :nodoc:
45
+ super
46
+ ensure
47
+ @notice_receiver_sql_warnings = []
48
+ end
46
49
 
47
- log(sql, name) do
48
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
49
- @connection.async_exec(sql)
50
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
51
+ log(sql, name, async: async) do
52
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
53
+ result = conn.async_exec(sql)
54
+ handle_warnings(result)
55
+ result
50
56
  end
51
57
  end
52
58
  end
53
59
 
54
- def exec_query(sql, name = "SQL", binds = [], prepare: false)
55
- execute_and_clear(sql, name, binds, prepare: prepare) do |result|
60
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
61
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
56
62
  types = {}
57
63
  fields = result.fields
58
64
  fields.each_with_index do |fname, i|
59
65
  ftype = result.ftype i
60
66
  fmod = result.fmod i
61
- case type = get_oid_type(ftype, fmod, fname)
62
- when Type::Integer, Type::Float, OID::Decimal, Type::String, Type::DateTime, Type::Boolean
63
- # skip if a column has already been type casted by pg decoders
64
- else types[fname] = type
65
- end
67
+ types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
66
68
  end
67
69
  build_result(columns: fields, rows: result.values, column_types: types)
68
70
  end
69
71
  end
70
72
 
71
- def exec_delete(sql, name = nil, binds = [])
73
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
72
74
  execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
73
75
  end
74
76
  alias :exec_update :exec_delete
75
77
 
76
- def sql_for_insert(sql, pk, binds) # :nodoc:
77
- if pk.nil?
78
- # Extract the table from the insert sql. Yuck.
79
- table_ref = extract_table_ref_from_insert_sql(sql)
80
- pk = primary_key(table_ref) if table_ref
81
- end
82
-
83
- if pk = suppress_composite_primary_key(pk)
84
- sql = "#{sql} RETURNING #{quote_column_name(pk)}"
85
- end
86
-
87
- super
88
- end
89
- private :sql_for_insert
90
-
91
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
78
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
92
79
  if use_insert_returning? || pk == false
93
80
  super
94
81
  else
95
- result = exec_query(sql, name, binds)
82
+ result = internal_exec_query(sql, name, binds)
96
83
  unless sequence_name
97
84
  table_ref = extract_table_ref_from_insert_sql(sql)
98
85
  if table_ref
@@ -107,26 +94,56 @@ module ActiveRecord
107
94
  end
108
95
 
109
96
  # Begins a transaction.
110
- def begin_db_transaction
111
- execute("BEGIN", "TRANSACTION")
97
+ def begin_db_transaction # :nodoc:
98
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
112
99
  end
113
100
 
114
- def begin_isolated_db_transaction(isolation)
115
- begin_db_transaction
116
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
101
+ def begin_isolated_db_transaction(isolation) # :nodoc:
102
+ internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
117
103
  end
118
104
 
119
105
  # Commits a transaction.
120
- def commit_db_transaction
121
- execute("COMMIT", "TRANSACTION")
106
+ def commit_db_transaction # :nodoc:
107
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
122
108
  end
123
109
 
124
110
  # Aborts a transaction.
125
- def exec_rollback_db_transaction
126
- execute("ROLLBACK", "TRANSACTION")
111
+ def exec_rollback_db_transaction # :nodoc:
112
+ cancel_any_running_query
113
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
114
+ end
115
+
116
+ def exec_restart_db_transaction # :nodoc:
117
+ cancel_any_running_query
118
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
119
+ end
120
+
121
+ # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
122
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
123
+ private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
124
+
125
+ def high_precision_current_timestamp
126
+ HIGH_PRECISION_CURRENT_TIMESTAMP
127
+ end
128
+
129
+ def build_explain_clause(options = [])
130
+ return "EXPLAIN" if options.empty?
131
+
132
+ "EXPLAIN (#{options.join(", ").upcase})"
127
133
  end
128
134
 
129
135
  private
136
+ IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
137
+ private_constant :IDLE_TRANSACTION_STATUSES
138
+
139
+ def cancel_any_running_query
140
+ return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
141
+
142
+ @raw_connection.cancel
143
+ @raw_connection.block
144
+ rescue PG::Error
145
+ end
146
+
130
147
  def execute_batch(statements, name = nil)
131
148
  execute(combine_multi_statements(statements))
132
149
  end
@@ -137,12 +154,29 @@ module ActiveRecord
137
154
 
138
155
  # Returns the current ID of a table's sequence.
139
156
  def last_insert_id_result(sequence_name)
140
- exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
157
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
158
+ end
159
+
160
+ def returning_column_values(result)
161
+ result.rows.first
141
162
  end
142
163
 
143
164
  def suppress_composite_primary_key(pk)
144
165
  pk unless pk.is_a?(Array)
145
166
  end
167
+
168
+ def handle_warnings(sql)
169
+ @notice_receiver_sql_warnings.each do |warning|
170
+ next if warning_ignored?(warning)
171
+
172
+ warning.sql = sql
173
+ ActiveRecord.db_warnings_action.call(warning)
174
+ end
175
+ end
176
+
177
+ def warning_ignored?(warning)
178
+ ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
179
+ end
146
180
  end
147
181
  end
148
182
  end
@@ -65,7 +65,7 @@ module ActiveRecord
65
65
  end
66
66
 
67
67
  def map(value, &block)
68
- value.map(&block)
68
+ value.map { |v| subtype.map(v, &block) }
69
69
  end
70
70
 
71
71
  def changed_in_place?(raw_old_value, new_value)
@@ -16,6 +16,14 @@ module ActiveRecord
16
16
  super
17
17
  end
18
18
  end
19
+
20
+ def type_cast_for_schema(value)
21
+ case value
22
+ when ::Float::INFINITY then "::Float::INFINITY"
23
+ when -::Float::INFINITY then "-::Float::INFINITY"
24
+ else super
25
+ end
26
+ end
19
27
  end
20
28
  end
21
29
  end
@@ -24,6 +24,11 @@ module ActiveRecord
24
24
  else super
25
25
  end
26
26
  end
27
+
28
+ protected
29
+ def real_type_unless_aliased(real_type)
30
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.datetime_type == real_type ? :datetime : real_type
31
+ end
27
32
  end
28
33
  end
29
34
  end