activerecord 7.0.8.1 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +642 -1925
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +3 -3
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +59 -17
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -1,70 +1,69 @@
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
- module ConnectionHandling # :nodoc:
11
- # Establishes a connection to the database that's used by all Active Record objects.
12
- 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
- )
28
- end
29
- end
30
-
31
10
  module ConnectionAdapters
11
+ # = Active Record MySQL2 Adapter
32
12
  class Mysql2Adapter < AbstractMysqlAdapter
33
- ER_BAD_DB_ERROR = 1049
34
- ER_ACCESS_DENIED_ERROR = 1045
35
- ER_CONN_HOST_ERROR = 2003
36
- ER_UNKNOWN_HOST_ERROR = 2005
13
+ ER_BAD_DB_ERROR = 1049
14
+ ER_DBACCESS_DENIED_ERROR = 1044
15
+ ER_ACCESS_DENIED_ERROR = 1045
16
+ ER_CONN_HOST_ERROR = 2003
17
+ ER_UNKNOWN_HOST_ERROR = 2005
37
18
 
38
19
  ADAPTER_NAME = "Mysql2"
39
20
 
40
- include MySQL::DatabaseStatements
21
+ include Mysql2::DatabaseStatements
41
22
 
42
23
  class << self
43
24
  def new_client(config)
44
- Mysql2::Client.new(config)
45
- rescue Mysql2::Error => error
46
- if error.error_number == ConnectionAdapters::Mysql2Adapter::ER_BAD_DB_ERROR
25
+ ::Mysql2::Client.new(config)
26
+ rescue ::Mysql2::Error => error
27
+ case error.error_number
28
+ when ER_BAD_DB_ERROR
47
29
  raise ActiveRecord::NoDatabaseError.db_error(config[:database])
48
- elsif error.error_number == ConnectionAdapters::Mysql2Adapter::ER_ACCESS_DENIED_ERROR
30
+ when ER_DBACCESS_DENIED_ERROR, ER_ACCESS_DENIED_ERROR
49
31
  raise ActiveRecord::DatabaseConnectionError.username_error(config[:username])
50
- elsif [ConnectionAdapters::Mysql2Adapter::ER_CONN_HOST_ERROR, ConnectionAdapters::Mysql2Adapter::ER_UNKNOWN_HOST_ERROR].include?(error.error_number)
32
+ when ER_CONN_HOST_ERROR, ER_UNKNOWN_HOST_ERROR
51
33
  raise ActiveRecord::DatabaseConnectionError.hostname_error(config[:host])
52
34
  else
53
35
  raise ActiveRecord::ConnectionNotEstablished, error.message
54
36
  end
55
37
  end
56
- end
57
38
 
58
- def initialize(connection, logger, connection_options, config)
59
- superclass_config = config.reverse_merge(prepared_statements: false)
60
- super(connection, logger, connection_options, superclass_config)
61
- configure_connection
39
+ private
40
+ def initialize_type_map(m)
41
+ super
42
+
43
+ m.register_type(%r(char)i) do |sql_type|
44
+ limit = extract_limit(sql_type)
45
+ Type.lookup(:string, adapter: :mysql2, limit: limit)
46
+ end
47
+
48
+ m.register_type %r(^enum)i, Type.lookup(:string, adapter: :mysql2)
49
+ m.register_type %r(^set)i, Type.lookup(:string, adapter: :mysql2)
50
+ end
62
51
  end
63
52
 
64
- def self.database_exists?(config)
65
- !!ActiveRecord::Base.mysql2_connection(config)
66
- rescue ActiveRecord::NoDatabaseError
67
- false
53
+ TYPE_MAP = Type::TypeMap.new.tap { |m| initialize_type_map(m) }
54
+
55
+ def initialize(...)
56
+ super
57
+
58
+ @config[:flags] ||= 0
59
+
60
+ if @config[:flags].kind_of? Array
61
+ @config[:flags].push "FOUND_ROWS"
62
+ else
63
+ @config[:flags] |= ::Mysql2::Client::FOUND_ROWS
64
+ end
65
+
66
+ @connection_parameters ||= @config
68
67
  end
69
68
 
70
69
  def supports_json?
@@ -83,6 +82,10 @@ module ActiveRecord
83
82
  true
84
83
  end
85
84
 
85
+ def savepoint_errors_invalidate_transactions?
86
+ true
87
+ end
88
+
86
89
  def supports_lazy_transactions?
87
90
  true
88
91
  end
@@ -102,69 +105,99 @@ module ActiveRecord
102
105
  end
103
106
 
104
107
  #--
105
- # QUOTING ==================================================
108
+ # CONNECTION MANAGEMENT ====================================
106
109
  #++
107
110
 
108
- def quote_string(string)
109
- @connection.escape(string)
110
- rescue Mysql2::Error => error
111
- raise translate_exception(error, message: error.message, sql: "<escape>", binds: [])
111
+ def connected?
112
+ !(@raw_connection.nil? || @raw_connection.closed?)
112
113
  end
113
114
 
114
- #--
115
- # CONNECTION MANAGEMENT ====================================
116
- #++
117
-
118
115
  def active?
119
- @connection.ping
116
+ connected? && @lock.synchronize { @raw_connection&.ping } || false
120
117
  end
121
118
 
122
- def reconnect!
123
- super
124
- disconnect!
125
- connect
126
- end
127
119
  alias :reset! :reconnect!
128
120
 
129
121
  # Disconnects from the database if already connected.
130
122
  # Otherwise, this method does nothing.
131
123
  def disconnect!
132
- super
133
- @connection.close
124
+ @lock.synchronize do
125
+ super
126
+ @raw_connection&.close
127
+ @raw_connection = nil
128
+ end
134
129
  end
135
130
 
136
131
  def discard! # :nodoc:
137
- super
138
- @connection.automatic_close = false
139
- @connection = nil
132
+ @lock.synchronize do
133
+ super
134
+ @raw_connection&.automatic_close = false
135
+ @raw_connection = nil
136
+ end
140
137
  end
141
138
 
142
139
  private
140
+ def text_type?(type)
141
+ TYPE_MAP.lookup(type).is_a?(Type::String) || TYPE_MAP.lookup(type).is_a?(Type::Text)
142
+ end
143
+
143
144
  def connect
144
- @connection = self.class.new_client(@config)
145
- configure_connection
145
+ @raw_connection = self.class.new_client(@connection_parameters)
146
+ rescue ConnectionNotEstablished => ex
147
+ raise ex.set_pool(@pool)
148
+ end
149
+
150
+ def reconnect
151
+ @lock.synchronize do
152
+ @raw_connection&.close
153
+ @raw_connection = nil
154
+ connect
155
+ end
146
156
  end
147
157
 
148
158
  def configure_connection
149
- @connection.query_options[:as] = :array
159
+ @raw_connection.query_options[:as] = :array
160
+ @raw_connection.query_options[:database_timezone] = default_timezone
150
161
  super
151
162
  end
152
163
 
153
164
  def full_version
154
- schema_cache.database_version.full_version_string
165
+ database_version.full_version_string
155
166
  end
156
167
 
157
168
  def get_full_version
158
- @connection.server_info[:version]
169
+ any_raw_connection.server_info[:version]
159
170
  end
160
171
 
161
172
  def translate_exception(exception, message:, sql:, binds:)
162
- if exception.is_a?(Mysql2::Error::TimeoutError) && !exception.error_number
163
- ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds)
173
+ if exception.is_a?(::Mysql2::Error::TimeoutError) && !exception.error_number
174
+ ActiveRecord::AdapterTimeout.new(message, sql: sql, binds: binds, connection_pool: @pool)
175
+ elsif exception.is_a?(::Mysql2::Error::ConnectionError)
176
+ if exception.message.match?(/MySQL client is not connected/i)
177
+ ActiveRecord::ConnectionNotEstablished.new(exception, connection_pool: @pool)
178
+ else
179
+ ActiveRecord::ConnectionFailed.new(message, sql: sql, binds: binds, connection_pool: @pool)
180
+ end
164
181
  else
165
182
  super
166
183
  end
167
184
  end
185
+
186
+ def default_prepared_statements
187
+ false
188
+ end
189
+
190
+ ActiveRecord::Type.register(:immutable_string, adapter: :mysql2) do |_, **args|
191
+ Type::ImmutableString.new(true: "1", false: "0", **args)
192
+ end
193
+
194
+ ActiveRecord::Type.register(:string, adapter: :mysql2) do |_, **args|
195
+ Type::String.new(true: "1", false: "0", **args)
196
+ end
197
+
198
+ ActiveRecord::Type.register(:unsigned_integer, Type::UnsignedInteger, adapter: :mysql2)
168
199
  end
200
+
201
+ ActiveSupport.run_load_hooks(:active_record_mysql2adapter, Mysql2Adapter)
169
202
  end
170
203
  end
@@ -3,10 +3,15 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  class PoolConfig # :nodoc:
6
- include Mutex_m
6
+ include MonitorMixin
7
7
 
8
- attr_reader :db_config, :connection_class, :role, :shard
9
- attr_accessor :schema_cache
8
+ attr_reader :db_config, :role, :shard
9
+ attr_writer :schema_reflection, :server_version
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,10 +20,15 @@ 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
29
  def initialize(connection_class, db_config, role, shard)
21
30
  super()
31
+ @server_version = nil
22
32
  @connection_class = connection_class
23
33
  @db_config = db_config
24
34
  @role = role
@@ -27,7 +37,11 @@ module ActiveRecord
27
37
  INSTANCES[self] = self
28
38
  end
29
39
 
30
- def connection_specification_name
40
+ def server_version(connection)
41
+ @server_version || synchronize { @server_version ||= connection.get_database_version }
42
+ end
43
+
44
+ def connection_name
31
45
  if connection_class.primary_class?
32
46
  "ActiveRecord::Base"
33
47
  else
@@ -35,15 +49,13 @@ module ActiveRecord
35
49
  end
36
50
  end
37
51
 
38
- def disconnect!
39
- ActiveSupport::ForkTracker.check!
40
-
52
+ def disconnect!(automatic_reconnect: false)
41
53
  return unless @pool
42
54
 
43
55
  synchronize do
44
56
  return unless @pool
45
57
 
46
- @pool.automatic_reconnect = false
58
+ @pool.automatic_reconnect = automatic_reconnect
47
59
  @pool.disconnect!
48
60
  end
49
61
 
@@ -51,8 +63,6 @@ module ActiveRecord
51
63
  end
52
64
 
53
65
  def pool
54
- ActiveSupport::ForkTracker.check!
55
-
56
66
  @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
57
67
  end
58
68
 
@@ -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
@@ -1,23 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/object/blank"
4
-
5
3
  module ActiveRecord
6
4
  module ConnectionAdapters
7
5
  module PostgreSQL
8
6
  class Column < ConnectionAdapters::Column # :nodoc:
9
7
  delegate :oid, :fmod, to: :sql_type_metadata
10
8
 
11
- def initialize(*, serial: nil, generated: nil, **)
9
+ def initialize(*, serial: nil, identity: nil, generated: nil, **)
12
10
  super
13
11
  @serial = serial
12
+ @identity = identity
14
13
  @generated = generated
15
14
  end
16
15
 
16
+ def identity?
17
+ @identity
18
+ end
19
+
17
20
  def serial?
18
21
  @serial
19
22
  end
20
23
 
24
+ def auto_incremented_by_db?
25
+ serial? || identity?
26
+ end
27
+
21
28
  def virtual?
22
29
  # We assume every generated column is virtual, no matter the concrete type
23
30
  @generated.present?
@@ -42,12 +49,14 @@ module ActiveRecord
42
49
 
43
50
  def init_with(coder)
44
51
  @serial = coder["serial"]
52
+ @identity = coder["identity"]
45
53
  @generated = coder["generated"]
46
54
  super
47
55
  end
48
56
 
49
57
  def encode_with(coder)
50
58
  coder["serial"] = @serial
59
+ coder["identity"] = @identity
51
60
  coder["generated"] = @generated
52
61
  super
53
62
  end
@@ -55,6 +64,7 @@ module ActiveRecord
55
64
  def ==(other)
56
65
  other.is_a?(Column) &&
57
66
  super &&
67
+ identity? == other.identity? &&
58
68
  serial? == other.serial?
59
69
  end
60
70
  alias :eql? :==
@@ -62,6 +72,7 @@ module ActiveRecord
62
72
  def hash
63
73
  Column.hash ^
64
74
  super.hash ^
75
+ identity?.hash ^
65
76
  serial?.hash
66
77
  end
67
78
  end
@@ -4,19 +4,22 @@ 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
14
  def query(sql, name = nil) # :nodoc:
14
- materialize_transactions
15
15
  mark_transaction_written_if_write(sql)
16
16
 
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
17
+ log(sql, name) do |notification_payload|
18
+ with_raw_connection do |conn|
19
+ result = conn.async_exec(sql).map_types!(@type_map_for_results).values
20
+ verified!
21
+ notification_payload[:row_count] = result.count
22
+ result
20
23
  end
21
24
  end
22
25
  end
@@ -34,24 +37,33 @@ module ActiveRecord
34
37
 
35
38
  # Executes an SQL statement, returning a PG::Result object on success
36
39
  # or raising a PG::Error exception otherwise.
40
+ #
41
+ # Setting +allow_retry+ to true causes the db to reconnect and retry
42
+ # executing the SQL statement in case of a connection-related exception.
43
+ # This option should only be enabled for known idempotent queries.
44
+ #
37
45
  # Note: the PG::Result object is manually memory managed; if you don't
38
46
  # need it specifically, you may want consider the <tt>exec_query</tt> wrapper.
39
- def execute(sql, name = nil)
40
- sql = transform_query(sql)
41
- check_if_write_query(sql)
42
-
43
- materialize_transactions
44
- mark_transaction_written_if_write(sql)
47
+ def execute(...) # :nodoc:
48
+ super
49
+ ensure
50
+ @notice_receiver_sql_warnings = []
51
+ end
45
52
 
46
- log(sql, name) do
47
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
48
- @connection.async_exec(sql)
53
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
54
+ log(sql, name, async: async) do |notification_payload|
55
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
56
+ result = conn.async_exec(sql)
57
+ verified!
58
+ handle_warnings(result)
59
+ notification_payload[:row_count] = result.count
60
+ result
49
61
  end
50
62
  end
51
63
  end
52
64
 
53
- def exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
54
- execute_and_clear(sql, name, binds, prepare: prepare, async: async) do |result|
65
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
66
+ execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
55
67
  types = {}
56
68
  fields = result.fields
57
69
  fields.each_with_index do |fname, i|
@@ -59,7 +71,7 @@ module ActiveRecord
59
71
  fmod = result.fmod i
60
72
  types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
61
73
  end
62
- build_result(columns: fields, rows: result.values, column_types: types)
74
+ build_result(columns: fields, rows: result.values, column_types: types.freeze)
63
75
  end
64
76
  end
65
77
 
@@ -68,26 +80,11 @@ module ActiveRecord
68
80
  end
69
81
  alias :exec_update :exec_delete
70
82
 
71
- def sql_for_insert(sql, pk, binds) # :nodoc:
72
- if pk.nil?
73
- # Extract the table from the insert sql. Yuck.
74
- table_ref = extract_table_ref_from_insert_sql(sql)
75
- pk = primary_key(table_ref) if table_ref
76
- end
77
-
78
- if pk = suppress_composite_primary_key(pk)
79
- sql = "#{sql} RETURNING #{quote_column_name(pk)}"
80
- end
81
-
82
- super
83
- end
84
- private :sql_for_insert
85
-
86
- def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil) # :nodoc:
83
+ def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
87
84
  if use_insert_returning? || pk == false
88
85
  super
89
86
  else
90
- result = exec_query(sql, name, binds)
87
+ result = internal_exec_query(sql, name, binds)
91
88
  unless sequence_name
92
89
  table_ref = extract_table_ref_from_insert_sql(sql)
93
90
  if table_ref
@@ -103,33 +100,76 @@ module ActiveRecord
103
100
 
104
101
  # Begins a transaction.
105
102
  def begin_db_transaction # :nodoc:
106
- execute("BEGIN", "TRANSACTION")
103
+ internal_execute("BEGIN", "TRANSACTION", allow_retry: true, materialize_transactions: false)
107
104
  end
108
105
 
109
106
  def begin_isolated_db_transaction(isolation) # :nodoc:
110
- begin_db_transaction
111
- execute "SET TRANSACTION ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}"
107
+ internal_execute("BEGIN ISOLATION LEVEL #{transaction_isolation_levels.fetch(isolation)}", "TRANSACTION", allow_retry: true, materialize_transactions: false)
112
108
  end
113
109
 
114
110
  # Commits a transaction.
115
111
  def commit_db_transaction # :nodoc:
116
- execute("COMMIT", "TRANSACTION")
112
+ internal_execute("COMMIT", "TRANSACTION", allow_retry: false, materialize_transactions: true)
117
113
  end
118
114
 
119
115
  # Aborts a transaction.
120
116
  def exec_rollback_db_transaction # :nodoc:
121
- execute("ROLLBACK", "TRANSACTION")
117
+ cancel_any_running_query
118
+ internal_execute("ROLLBACK", "TRANSACTION", allow_retry: false, materialize_transactions: true)
119
+ end
120
+
121
+ def exec_restart_db_transaction # :nodoc:
122
+ cancel_any_running_query
123
+ internal_execute("ROLLBACK AND CHAIN", "TRANSACTION", allow_retry: false, materialize_transactions: true)
122
124
  end
123
125
 
124
126
  # From https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-CURRENT
125
- HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP").freeze # :nodoc:
127
+ HIGH_PRECISION_CURRENT_TIMESTAMP = Arel.sql("CURRENT_TIMESTAMP", retryable: true).freeze # :nodoc:
126
128
  private_constant :HIGH_PRECISION_CURRENT_TIMESTAMP
127
129
 
128
130
  def high_precision_current_timestamp
129
131
  HIGH_PRECISION_CURRENT_TIMESTAMP
130
132
  end
131
133
 
134
+ def build_explain_clause(options = [])
135
+ return "EXPLAIN" if options.empty?
136
+
137
+ "EXPLAIN (#{options.join(", ").upcase})"
138
+ end
139
+
140
+ # Set when constraints will be checked for the current transaction.
141
+ #
142
+ # Not passing any specific constraint names will set the value for all deferrable constraints.
143
+ #
144
+ # [<tt>deferred</tt>]
145
+ # Valid values are +:deferred+ or +:immediate+.
146
+ #
147
+ # See https://www.postgresql.org/docs/current/sql-set-constraints.html
148
+ def set_constraints(deferred, *constraints)
149
+ unless %i[deferred immediate].include?(deferred)
150
+ raise ArgumentError, "deferred must be :deferred or :immediate"
151
+ end
152
+
153
+ constraints = if constraints.empty?
154
+ "ALL"
155
+ else
156
+ constraints.map { |c| quote_table_name(c) }.join(", ")
157
+ end
158
+ execute("SET CONSTRAINTS #{constraints} #{deferred.to_s.upcase}")
159
+ end
160
+
132
161
  private
162
+ IDLE_TRANSACTION_STATUSES = [PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR]
163
+ private_constant :IDLE_TRANSACTION_STATUSES
164
+
165
+ def cancel_any_running_query
166
+ return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
167
+
168
+ @raw_connection.cancel
169
+ @raw_connection.block
170
+ rescue PG::Error
171
+ end
172
+
133
173
  def execute_batch(statements, name = nil)
134
174
  execute(combine_multi_statements(statements))
135
175
  end
@@ -140,12 +180,29 @@ module ActiveRecord
140
180
 
141
181
  # Returns the current ID of a table's sequence.
142
182
  def last_insert_id_result(sequence_name)
143
- exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
183
+ internal_exec_query("SELECT currval(#{quote(sequence_name)})", "SQL")
184
+ end
185
+
186
+ def returning_column_values(result)
187
+ result.rows.first
144
188
  end
145
189
 
146
190
  def suppress_composite_primary_key(pk)
147
191
  pk unless pk.is_a?(Array)
148
192
  end
193
+
194
+ def handle_warnings(sql)
195
+ @notice_receiver_sql_warnings.each do |warning|
196
+ next if warning_ignored?(warning)
197
+
198
+ warning.sql = sql
199
+ ActiveRecord.db_warnings_action.call(warning)
200
+ end
201
+ end
202
+
203
+ def warning_ignored?(warning)
204
+ ["WARNING", "ERROR", "FATAL", "PANIC"].exclude?(warning.level) || super
205
+ end
149
206
  end
150
207
  end
151
208
  end
@@ -28,6 +28,12 @@ module ActiveRecord
28
28
  end
29
29
  end
30
30
 
31
+ # TODO: Remove when IPAddr#== compares IPAddr#prefix. See
32
+ # https://github.com/ruby/ipaddr/issues/21
33
+ def changed?(old_value, new_value, _new_value_before_type_cast)
34
+ !old_value.eql?(new_value) || !old_value.nil? && old_value.prefix != new_value.prefix
35
+ end
36
+
31
37
  def cast_value(value)
32
38
  if value.nil?
33
39
  nil