activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  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 +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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  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 +2 -4
  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 +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ module Mysql2
6
+ module DatabaseStatements
7
+ # Returns an ActiveRecord::Result instance.
8
+ def select_all(*, **) # :nodoc:
9
+ if ExplainRegistry.collect? && prepared_statements
10
+ unprepared_statement { super }
11
+ else
12
+ super
13
+ end
14
+ end
15
+
16
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
17
+ if without_prepared_statement?(binds)
18
+ execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
19
+ if result
20
+ build_result(columns: result.fields, rows: result.to_a)
21
+ else
22
+ build_result(columns: [], rows: [])
23
+ end
24
+ end
25
+ else
26
+ exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async) do |_, result|
27
+ if result
28
+ build_result(columns: result.fields, rows: result.to_a)
29
+ else
30
+ build_result(columns: [], rows: [])
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def exec_delete(sql, name = nil, binds = []) # :nodoc:
37
+ if without_prepared_statement?(binds)
38
+ with_raw_connection do |conn|
39
+ @affected_rows_before_warnings = nil
40
+ execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
41
+ end
42
+ else
43
+ exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
44
+ end
45
+ end
46
+ alias :exec_update :exec_delete
47
+
48
+ private
49
+ def sync_timezone_changes(raw_connection)
50
+ raw_connection.query_options[:database_timezone] = default_timezone
51
+ end
52
+
53
+ def execute_batch(statements, name = nil)
54
+ statements = statements.map { |sql| transform_query(sql) }
55
+ combine_multi_statements(statements).each do |statement|
56
+ with_raw_connection do |conn|
57
+ raw_execute(statement, name)
58
+ end
59
+ end
60
+ end
61
+
62
+ def last_inserted_id(result)
63
+ if supports_insert_returning?
64
+ super
65
+ else
66
+ @raw_connection&.last_id
67
+ end
68
+ end
69
+
70
+ def multi_statements_enabled?
71
+ flags = @config[:flags]
72
+
73
+ if flags.is_a?(Array)
74
+ flags.include?("MULTI_STATEMENTS")
75
+ else
76
+ flags.anybits?(::Mysql2::Client::MULTI_STATEMENTS)
77
+ end
78
+ end
79
+
80
+ def with_multi_statements
81
+ if multi_statements_enabled?
82
+ return yield
83
+ end
84
+
85
+ with_raw_connection do |conn|
86
+ conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
87
+
88
+ yield
89
+ ensure
90
+ conn.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
91
+ end
92
+ end
93
+
94
+ def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
95
+ log(sql, name, async: async) do |notification_payload|
96
+ with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
97
+ sync_timezone_changes(conn)
98
+ result = conn.query(sql)
99
+ conn.abandon_results!
100
+ verified!
101
+ handle_warnings(sql)
102
+ notification_payload[:row_count] = result&.size || 0
103
+ result
104
+ end
105
+ end
106
+ end
107
+
108
+ def exec_stmt_and_free(sql, name, binds, cache_stmt: false, async: false)
109
+ sql = transform_query(sql)
110
+ check_if_write_query(sql)
111
+
112
+ mark_transaction_written_if_write(sql)
113
+
114
+ type_casted_binds = type_casted_binds(binds)
115
+
116
+ log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
117
+ with_raw_connection do |conn|
118
+ sync_timezone_changes(conn)
119
+
120
+ if cache_stmt
121
+ stmt = @statements[sql] ||= conn.prepare(sql)
122
+ else
123
+ stmt = conn.prepare(sql)
124
+ end
125
+
126
+ begin
127
+ result = ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
128
+ stmt.execute(*type_casted_binds)
129
+ end
130
+ verified!
131
+ result
132
+ rescue ::Mysql2::Error => e
133
+ if cache_stmt
134
+ @statements.delete(sql)
135
+ else
136
+ stmt.close
137
+ end
138
+ raise e
139
+ end
140
+
141
+ ret = yield stmt, result
142
+ notification_payload[:row_count] = result&.size || 0
143
+ result.free if result
144
+ stmt.close unless cache_stmt
145
+ ret
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -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,17 +49,22 @@ module ActiveRecord
42
49
 
43
50
  def init_with(coder)
44
51
  @serial = coder["serial"]
52
+ @identity = coder["identity"]
53
+ @generated = coder["generated"]
45
54
  super
46
55
  end
47
56
 
48
57
  def encode_with(coder)
49
58
  coder["serial"] = @serial
59
+ coder["identity"] = @identity
60
+ coder["generated"] = @generated
50
61
  super
51
62
  end
52
63
 
53
64
  def ==(other)
54
65
  other.is_a?(Column) &&
55
66
  super &&
67
+ identity? == other.identity? &&
56
68
  serial? == other.serial?
57
69
  end
58
70
  alias :eql? :==
@@ -60,6 +72,7 @@ module ActiveRecord
60
72
  def hash
61
73
  Column.hash ^
62
74
  super.hash ^
75
+ identity?.hash ^
63
76
  serial?.hash
64
77
  end
65
78
  end