activerecord 6.0.0 → 6.1.4

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

Potentially problematic release.


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

Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1178 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_record/aggregations.rb +5 -6
  6. data/lib/active_record/association_relation.rb +30 -10
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +55 -29
  9. data/lib/active_record/associations/association_scope.rb +19 -15
  10. data/lib/active_record/associations/belongs_to_association.rb +23 -10
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  12. data/lib/active_record/associations/builder/association.rb +32 -5
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +25 -8
  20. data/lib/active_record/associations/collection_proxy.rb +14 -7
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -3
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  26. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/preloader/association.rb +51 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +120 -13
  34. data/lib/active_record/attribute_assignment.rb +10 -9
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  36. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  37. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  38. data/lib/active_record/attribute_methods/query.rb +3 -6
  39. data/lib/active_record/attribute_methods/read.rb +8 -12
  40. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  42. data/lib/active_record/attribute_methods/write.rb +12 -21
  43. data/lib/active_record/attribute_methods.rb +64 -54
  44. data/lib/active_record/attributes.rb +33 -9
  45. data/lib/active_record/autosave_association.rb +63 -44
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +153 -24
  48. data/lib/active_record/coders/yaml_column.rb +12 -3
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +87 -38
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +141 -52
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +94 -36
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +76 -79
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  62. data/lib/active_record/connection_adapters/column.rb +15 -1
  63. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  64. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  65. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  75. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  79. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  102. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -65
  104. data/lib/active_record/connection_adapters/schema_cache.rb +106 -15
  105. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  106. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  107. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  108. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  109. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  110. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  111. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  112. data/lib/active_record/connection_adapters.rb +52 -0
  113. data/lib/active_record/connection_handling.rb +219 -81
  114. data/lib/active_record/core.rb +268 -71
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  117. data/lib/active_record/database_configurations/database_config.rb +52 -9
  118. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  119. data/lib/active_record/database_configurations/url_config.rb +15 -41
  120. data/lib/active_record/database_configurations.rb +124 -85
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +80 -38
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +58 -12
  134. data/lib/active_record/gem_version.rb +2 -2
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +43 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +33 -18
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +28 -9
  143. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector.rb +4 -2
  146. data/lib/active_record/migration/command_recorder.rb +53 -45
  147. data/lib/active_record/migration/compatibility.rb +71 -20
  148. data/lib/active_record/migration/join_table.rb +0 -1
  149. data/lib/active_record/migration.rb +115 -85
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/console_sandbox.rb +2 -4
  159. data/lib/active_record/railties/databases.rake +280 -99
  160. data/lib/active_record/readonly_attributes.rb +4 -0
  161. data/lib/active_record/reflection.rb +77 -63
  162. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  163. data/lib/active_record/relation/batches.rb +38 -32
  164. data/lib/active_record/relation/calculations.rb +106 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  173. data/lib/active_record/relation/predicate_builder.rb +59 -40
  174. data/lib/active_record/relation/query_methods.rb +344 -181
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +111 -62
  178. data/lib/active_record/relation.rb +116 -82
  179. data/lib/active_record/result.rb +41 -34
  180. data/lib/active_record/runtime_registry.rb +2 -2
  181. data/lib/active_record/sanitization.rb +6 -17
  182. data/lib/active_record/schema_dumper.rb +34 -4
  183. data/lib/active_record/schema_migration.rb +2 -8
  184. data/lib/active_record/scoping/default.rb +1 -4
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/scoping.rb +0 -1
  187. data/lib/active_record/secure_token.rb +16 -8
  188. data/lib/active_record/serialization.rb +5 -3
  189. data/lib/active_record/signed_id.rb +116 -0
  190. data/lib/active_record/statement_cache.rb +20 -4
  191. data/lib/active_record/store.rb +3 -3
  192. data/lib/active_record/suppressor.rb +2 -2
  193. data/lib/active_record/table_metadata.rb +42 -36
  194. data/lib/active_record/tasks/database_tasks.rb +140 -113
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  197. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  198. data/lib/active_record/test_databases.rb +5 -4
  199. data/lib/active_record/test_fixtures.rb +79 -16
  200. data/lib/active_record/timestamp.rb +4 -7
  201. data/lib/active_record/touch_later.rb +20 -21
  202. data/lib/active_record/transactions.rb +26 -73
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type.rb +8 -2
  210. data/lib/active_record/type_caster/connection.rb +0 -1
  211. data/lib/active_record/type_caster/map.rb +8 -5
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/active_record/validations.rb +3 -3
  216. data/lib/active_record.rb +7 -13
  217. data/lib/arel/attributes/attribute.rb +4 -0
  218. data/lib/arel/collectors/bind.rb +5 -0
  219. data/lib/arel/collectors/composite.rb +8 -0
  220. data/lib/arel/collectors/sql_string.rb +7 -0
  221. data/lib/arel/collectors/substitute_binds.rb +7 -0
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/nodes.rb +3 -1
  237. data/lib/arel/predications.rb +17 -24
  238. data/lib/arel/select_manager.rb +1 -2
  239. data/lib/arel/table.rb +13 -5
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/arel/visitors.rb +0 -7
  247. data/lib/arel.rb +15 -12
  248. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  249. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  250. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  251. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  252. data/lib/rails/generators/active_record/migration.rb +6 -2
  253. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  254. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  255. metadata +27 -24
  256. data/lib/active_record/attribute_decorators.rb +0 -90
  257. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  258. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  259. data/lib/active_record/define_callbacks.rb +0 -22
  260. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  261. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  262. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  263. data/lib/arel/attributes.rb +0 -22
  264. data/lib/arel/visitors/depth_first.rb +0 -204
  265. data/lib/arel/visitors/ibm_db.rb +0 -34
  266. data/lib/arel/visitors/informix.rb +0 -62
  267. data/lib/arel/visitors/mssql.rb +0 -157
  268. data/lib/arel/visitors/oracle.rb +0 -159
  269. data/lib/arel/visitors/oracle12.rb +0 -66
  270. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module DatabaseStatements
7
7
  # Returns an ActiveRecord::Result instance.
8
- def select_all(*) # :nodoc:
8
+ def select_all(*, **) # :nodoc:
9
9
  result = if ExplainRegistry.collect? && prepared_statements
10
10
  unprepared_statement { super }
11
11
  else
@@ -19,13 +19,24 @@ module ActiveRecord
19
19
  execute(sql, name).to_a
20
20
  end
21
21
 
22
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
22
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
+ :desc, :describe, :set, :show, :use
24
+ ) # :nodoc:
23
25
  private_constant :READ_QUERY
24
26
 
25
27
  def write_query?(sql) # :nodoc:
26
28
  !READ_QUERY.match?(sql)
27
29
  end
28
30
 
31
+ def explain(arel, binds = [])
32
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
33
+ start = Concurrent.monotonic_time
34
+ result = exec_query(sql, "EXPLAIN", binds)
35
+ elapsed = Concurrent.monotonic_time - start
36
+
37
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
38
+ end
39
+
29
40
  # Executes the SQL statement in the context of this connection.
30
41
  def execute(sql, name = nil)
31
42
  if preventing_writes? && write_query?(sql)
@@ -43,17 +54,17 @@ module ActiveRecord
43
54
  if without_prepared_statement?(binds)
44
55
  execute_and_free(sql, name) do |result|
45
56
  if result
46
- ActiveRecord::Result.new(result.fields, result.to_a)
57
+ build_result(columns: result.fields, rows: result.to_a)
47
58
  else
48
- ActiveRecord::Result.new([], [])
59
+ build_result(columns: [], rows: [])
49
60
  end
50
61
  end
51
62
  else
52
63
  exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
53
64
  if result
54
- ActiveRecord::Result.new(result.fields, result.to_a)
65
+ build_result(columns: result.fields, rows: result.to_a)
55
66
  else
56
- ActiveRecord::Result.new([], [])
67
+ build_result(columns: [], rows: [])
57
68
  end
58
69
  end
59
70
  end
@@ -71,8 +82,10 @@ module ActiveRecord
71
82
  alias :exec_update :exec_delete
72
83
 
73
84
  private
74
- def execute_batch(sql, name = nil)
75
- super
85
+ def execute_batch(statements, name = nil)
86
+ combine_multi_statements(statements).each do |statement|
87
+ execute(statement, name)
88
+ end
76
89
  @connection.abandon_results!
77
90
  end
78
91
 
@@ -84,47 +97,27 @@ module ActiveRecord
84
97
  @connection.last_id
85
98
  end
86
99
 
87
- def supports_set_server_option?
88
- @connection.respond_to?(:set_server_option)
89
- end
90
-
91
- def build_truncate_statements(*table_names)
92
- if table_names.size == 1
93
- super.first
94
- else
95
- super
96
- end
97
- end
100
+ def multi_statements_enabled?
101
+ flags = @config[:flags]
98
102
 
99
- def multi_statements_enabled?(flags)
100
103
  if flags.is_a?(Array)
101
104
  flags.include?("MULTI_STATEMENTS")
102
105
  else
103
- (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
106
+ flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
104
107
  end
105
108
  end
106
109
 
107
110
  def with_multi_statements
108
- previous_flags = @config[:flags]
111
+ multi_statements_was = multi_statements_enabled?
109
112
 
110
- unless multi_statements_enabled?(previous_flags)
111
- if supports_set_server_option?
112
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
113
- else
114
- @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
115
- reconnect!
116
- end
113
+ unless multi_statements_was
114
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
117
115
  end
118
116
 
119
117
  yield
120
118
  ensure
121
- unless multi_statements_enabled?(previous_flags)
122
- if supports_set_server_option?
123
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
124
- else
125
- @config[:flags] = previous_flags
126
- reconnect!
127
- end
119
+ unless multi_statements_was
120
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
128
121
  end
129
122
  end
130
123
 
@@ -161,6 +154,7 @@ module ActiveRecord
161
154
  end
162
155
 
163
156
  materialize_transactions
157
+ mark_transaction_written_if_write(sql)
164
158
 
165
159
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
166
160
  # made since we established the connection
@@ -37,7 +37,6 @@ module ActiveRecord
37
37
  end
38
38
 
39
39
  private
40
-
41
40
  def compute_column_widths(result)
42
41
  [].tap do |widths|
43
42
  result.columns.each_with_index do |column, i|
@@ -57,7 +56,7 @@ module ActiveRecord
57
56
  items.each_with_index do |item, i|
58
57
  item = "NULL" if item.nil?
59
58
  justifier = item.is_a?(Numeric) ? "rjust" : "ljust"
60
- cells << item.to_s.send(justifier, widths[i])
59
+ cells << item.to_s.public_send(justifier, widths[i])
61
60
  end
62
61
  "| " + cells.join(" | ") + " |"
63
62
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/time_with_zone"
4
+
3
5
  module ActiveRecord
4
6
  module ConnectionAdapters
5
7
  module MySQL
@@ -47,7 +49,7 @@ module ActiveRecord
47
49
  # `table_name`.`column_name` | function(one or no argument)
48
50
  ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
49
51
  )
50
- (?:\s+AS\s+(?:\w+|`\w+`))?
52
+ (?:(?:\s+AS)?\s+(?:\w+|`\w+`))?
51
53
  )
52
54
  (?:\s*,\s*\g<1>)*
53
55
  \z
@@ -69,10 +71,23 @@ module ActiveRecord
69
71
  private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
70
72
 
71
73
  private
74
+ # Override +_type_cast+ we pass to mysql2 Date and Time objects instead
75
+ # of Strings since mysql2 is able to handle those classes more efficiently.
72
76
  def _type_cast(value)
73
77
  case value
74
- when Date, Time then value
75
- else super
78
+ when ActiveSupport::TimeWithZone
79
+ # We need to check explicitly for ActiveSupport::TimeWithZone because
80
+ # we need to transform it to Time objects but we don't want to
81
+ # transform Time objects to themselves.
82
+ if ActiveRecord::Base.default_timezone == :utc
83
+ value.getutc
84
+ else
85
+ value.getlocal
86
+ end
87
+ when Date, Time
88
+ value
89
+ else
90
+ super
76
91
  end
77
92
  end
78
93
  end
@@ -3,15 +3,18 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module MySQL
6
- class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc:
6
+ class SchemaCreation < SchemaCreation # :nodoc:
7
7
  delegate :add_sql_comment!, :mariadb?, to: :@conn, private: true
8
8
 
9
9
  private
10
-
11
10
  def visit_DropForeignKey(name)
12
11
  "DROP FOREIGN KEY #{name}"
13
12
  end
14
13
 
14
+ def visit_DropCheckConstraint(name)
15
+ "DROP #{mariadb? ? 'CONSTRAINT' : 'CHECK'} #{name}"
16
+ end
17
+
15
18
  def visit_AddColumnDefinition(o)
16
19
  add_column_position!(super, column_options(o.column))
17
20
  end
@@ -21,15 +24,37 @@ module ActiveRecord
21
24
  add_column_position!(change_column_sql, column_options(o.column))
22
25
  end
23
26
 
24
- def add_table_options!(create_sql, options)
25
- add_sql_comment!(super, options[:comment])
27
+ def visit_CreateIndexDefinition(o)
28
+ sql = visit_IndexDefinition(o.index, true)
29
+ sql << " #{o.algorithm}" if o.algorithm
30
+ sql
31
+ end
32
+
33
+ def visit_IndexDefinition(o, create = false)
34
+ index_type = o.type&.to_s&.upcase || o.unique && "UNIQUE"
35
+
36
+ sql = create ? ["CREATE"] : []
37
+ sql << index_type if index_type
38
+ sql << "INDEX"
39
+ sql << quote_column_name(o.name)
40
+ sql << "USING #{o.using}" if o.using
41
+ sql << "ON #{quote_table_name(o.table)}" if create
42
+ sql << "(#{quoted_columns(o)})"
43
+
44
+ add_sql_comment!(sql.join(" "), o.comment)
45
+ end
46
+
47
+ def add_table_options!(create_sql, o)
48
+ create_sql << " DEFAULT CHARSET=#{o.charset}" if o.charset
49
+ create_sql << " COLLATE=#{o.collation}" if o.collation
50
+ add_sql_comment!(super, o.comment)
26
51
  end
27
52
 
28
53
  def add_column_options!(sql, options)
29
54
  # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
30
55
  # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
31
56
  # column to contain NULL, explicitly declare it with the NULL attribute.
32
- # See https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
57
+ # See https://dev.mysql.com/doc/refman/en/timestamp-initialization.html
33
58
  if /\Atimestamp\b/.match?(options[:column].sql_type) && !options[:primary_key]
34
59
  sql << " NULL" unless options[:null] == false || options_include_default?(options)
35
60
  end
@@ -63,8 +88,8 @@ module ActiveRecord
63
88
  end
64
89
 
65
90
  def index_in_create(table_name, column_name, options)
66
- index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
67
- add_sql_comment!((+"#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})"), comment)
91
+ index, _ = @conn.add_index_options(table_name, column_name, **options)
92
+ accept(index)
68
93
  end
69
94
  end
70
95
  end
@@ -60,6 +60,14 @@ module ActiveRecord
60
60
  class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
61
61
  include ColumnMethods
62
62
 
63
+ attr_reader :charset, :collation
64
+
65
+ def initialize(conn, name, charset: nil, collation: nil, **)
66
+ super
67
+ @charset = charset
68
+ @collation = collation
69
+ end
70
+
63
71
  def new_column_definition(name, type, **options) # :nodoc:
64
72
  case type
65
73
  when :virtual
@@ -49,7 +49,7 @@ module ActiveRecord
49
49
  end
50
50
 
51
51
  def schema_limit(column)
52
- super unless /\A(?:enum|set|(?:tiny|medium|long)?(?:text|blob))\b/.match?(column.sql_type)
52
+ super unless /\A(?:tiny|medium|long)?(?:text|blob)\b/.match?(column.sql_type)
53
53
  end
54
54
 
55
55
  def schema_precision(column)
@@ -79,7 +79,10 @@ module ActiveRecord
79
79
  " WHERE table_schema = #{scope[:schema]}" \
80
80
  " AND table_name = #{scope[:name]}" \
81
81
  " AND column_name = #{column_name}"
82
- @connection.query_value(sql, "SCHEMA").inspect
82
+ # Calling .inspect leads into issues with the query result
83
+ # which already returns escaped quotes.
84
+ # We remove the escape sequence from the result in order to deal with double escaping issues.
85
+ @connection.query_value(sql, "SCHEMA").gsub("\\'", "'").inspect
83
86
  end
84
87
  end
85
88
  end
@@ -51,26 +51,26 @@ module ActiveRecord
51
51
  end
52
52
 
53
53
  indexes.map do |index|
54
- options = index.last
54
+ options = index.pop
55
55
 
56
56
  if expressions = options.delete(:expressions)
57
57
  orders = options.delete(:orders)
58
58
  lengths = options.delete(:lengths)
59
59
 
60
- columns = index[-2].map { |name|
60
+ columns = index[-1].map { |name|
61
61
  [ name.to_sym, expressions[name] || +quote_column_name(name) ]
62
62
  }.to_h
63
63
 
64
- index[-2] = add_options_for_index_columns(
64
+ index[-1] = add_options_for_index_columns(
65
65
  columns, order: orders, length: lengths
66
66
  ).values.join(", ")
67
67
  end
68
68
 
69
- IndexDefinition.new(*index)
69
+ IndexDefinition.new(*index, **options)
70
70
  end
71
71
  end
72
72
 
73
- def remove_column(table_name, column_name, type = nil, options = {})
73
+ def remove_column(table_name, column_name, type = nil, **options)
74
74
  if foreign_key_exists?(table_name, column: column_name)
75
75
  remove_foreign_key(table_name, column: column_name)
76
76
  end
@@ -122,7 +122,7 @@ module ActiveRecord
122
122
  end
123
123
 
124
124
  def table_alias_length
125
- 256 # https://dev.mysql.com/doc/refman/8.0/en/identifiers.html
125
+ 256 # https://dev.mysql.com/doc/refman/en/identifiers.html
126
126
  end
127
127
 
128
128
  private
@@ -154,8 +154,8 @@ module ActiveRecord
154
154
  MySQL::SchemaCreation.new(self)
155
155
  end
156
156
 
157
- def create_table_definition(*args)
158
- MySQL::TableDefinition.new(self, *args)
157
+ def create_table_definition(name, **options)
158
+ MySQL::TableDefinition.new(self, name, **options)
159
159
  end
160
160
 
161
161
  def new_column_from_field(table_name, field)
@@ -196,17 +196,21 @@ module ActiveRecord
196
196
  end
197
197
 
198
198
  def add_options_for_index_columns(quoted_columns, **options)
199
- quoted_columns = add_index_length(quoted_columns, options)
199
+ quoted_columns = add_index_length(quoted_columns, **options)
200
200
  super
201
201
  end
202
202
 
203
203
  def data_source_sql(name = nil, type: nil)
204
204
  scope = quoted_scope(name, type: type)
205
205
 
206
- sql = +"SELECT table_name FROM information_schema.tables"
207
- sql << " WHERE table_schema = #{scope[:schema]}"
208
- sql << " AND table_name = #{scope[:name]}" if scope[:name]
209
- sql << " AND table_type = #{scope[:type]}" if scope[:type]
206
+ sql = +"SELECT table_name FROM (SELECT * FROM information_schema.tables "
207
+ sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
208
+ if scope[:type] || scope[:name]
209
+ conditions = []
210
+ conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
211
+ conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
212
+ sql << " WHERE #{conditions.join(" AND ")}"
213
+ end
210
214
  sql
211
215
  end
212
216
 
@@ -6,9 +6,11 @@ module ActiveRecord
6
6
  class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
7
7
  undef to_yaml if method_defined?(:to_yaml)
8
8
 
9
+ include Deduplicable
10
+
9
11
  attr_reader :extra
10
12
 
11
- def initialize(type_metadata, extra: "")
13
+ def initialize(type_metadata, extra: nil)
12
14
  super(type_metadata)
13
15
  @extra = extra
14
16
  end
@@ -25,6 +27,13 @@ module ActiveRecord
25
27
  __getobj__.hash ^
26
28
  extra.hash
27
29
  end
30
+
31
+ private
32
+ def deduplicated
33
+ __setobj__(__getobj__.deduplicate)
34
+ @extra = -extra if extra
35
+ super
36
+ end
28
37
  end
29
38
  end
30
39
  end
@@ -3,13 +3,11 @@
3
3
  require "active_record/connection_adapters/abstract_mysql_adapter"
4
4
  require "active_record/connection_adapters/mysql/database_statements"
5
5
 
6
- gem "mysql2", ">= 0.4.4"
6
+ gem "mysql2", "~> 0.5"
7
7
  require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
10
  module ConnectionHandling # :nodoc:
11
- ER_BAD_DB_ERROR = 1049
12
-
13
11
  # Establishes a connection to the database that's used by all Active Record objects.
14
12
  def mysql2_connection(config)
15
13
  config = config.symbolize_keys
@@ -21,23 +19,34 @@ module ActiveRecord
21
19
  config[:flags] |= Mysql2::Client::FOUND_ROWS
22
20
  end
23
21
 
24
- client = Mysql2::Client.new(config)
25
- ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
26
- rescue Mysql2::Error => error
27
- if error.error_number == ER_BAD_DB_ERROR
28
- raise ActiveRecord::NoDatabaseError
29
- else
30
- raise
31
- end
22
+ ConnectionAdapters::Mysql2Adapter.new(
23
+ ConnectionAdapters::Mysql2Adapter.new_client(config),
24
+ logger,
25
+ nil,
26
+ config,
27
+ )
32
28
  end
33
29
  end
34
30
 
35
31
  module ConnectionAdapters
36
32
  class Mysql2Adapter < AbstractMysqlAdapter
33
+ ER_BAD_DB_ERROR = 1049
37
34
  ADAPTER_NAME = "Mysql2"
38
35
 
39
36
  include MySQL::DatabaseStatements
40
37
 
38
+ class << self
39
+ 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
44
+ else
45
+ raise ActiveRecord::ConnectionNotEstablished, error.message
46
+ end
47
+ end
48
+ end
49
+
41
50
  def initialize(connection, logger, connection_options, config)
42
51
  superclass_config = config.reverse_merge(prepared_statements: false)
43
52
  super(connection, logger, connection_options, superclass_config)
@@ -92,6 +101,8 @@ module ActiveRecord
92
101
 
93
102
  def quote_string(string)
94
103
  @connection.escape(string)
104
+ rescue Mysql2::Error => error
105
+ raise translate_exception(error, message: error.message, sql: "<escape>", binds: [])
95
106
  end
96
107
 
97
108
  #--
@@ -123,9 +134,8 @@ module ActiveRecord
123
134
  end
124
135
 
125
136
  private
126
-
127
137
  def connect
128
- @connection = Mysql2::Client.new(@config)
138
+ @connection = self.class.new_client(@config)
129
139
  configure_connection
130
140
  end
131
141
 
@@ -141,6 +151,14 @@ module ActiveRecord
141
151
  def get_full_version
142
152
  @connection.server_info[:version]
143
153
  end
154
+
155
+ 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)
158
+ else
159
+ super
160
+ end
161
+ end
144
162
  end
145
163
  end
146
164
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class PoolConfig # :nodoc:
6
+ include Mutex_m
7
+
8
+ attr_reader :db_config, :connection_klass
9
+ attr_accessor :schema_cache
10
+
11
+ INSTANCES = ObjectSpace::WeakMap.new
12
+ private_constant :INSTANCES
13
+
14
+ class << self
15
+ def discard_pools!
16
+ INSTANCES.each_key(&:discard_pool!)
17
+ end
18
+ end
19
+
20
+ def initialize(connection_klass, db_config)
21
+ super()
22
+ @connection_klass = connection_klass
23
+ @db_config = db_config
24
+ @pool = nil
25
+ INSTANCES[self] = self
26
+ end
27
+
28
+ def connection_specification_name
29
+ if connection_klass.is_a?(String)
30
+ connection_klass
31
+ elsif connection_klass.primary_class?
32
+ "ActiveRecord::Base"
33
+ else
34
+ connection_klass.name
35
+ end
36
+ end
37
+
38
+ def disconnect!
39
+ ActiveSupport::ForkTracker.check!
40
+
41
+ return unless @pool
42
+
43
+ synchronize do
44
+ return unless @pool
45
+
46
+ @pool.automatic_reconnect = false
47
+ @pool.disconnect!
48
+ end
49
+
50
+ nil
51
+ end
52
+
53
+ def pool
54
+ ActiveSupport::ForkTracker.check!
55
+
56
+ @pool || synchronize { @pool ||= ConnectionAdapters::ConnectionPool.new(self) }
57
+ end
58
+
59
+ def discard_pool!
60
+ return unless @pool
61
+
62
+ synchronize do
63
+ return unless @pool
64
+
65
+ @pool.discard!
66
+ @pool = nil
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ ActiveSupport::ForkTracker.after_fork { ActiveRecord::ConnectionAdapters::PoolConfig.discard_pools! }
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module ConnectionAdapters
5
+ class PoolManager # :nodoc:
6
+ def initialize
7
+ @name_to_role_mapping = Hash.new { |h, k| h[k] = {} }
8
+ end
9
+
10
+ def shard_names
11
+ @name_to_role_mapping.values.flat_map { |shard_map| shard_map.keys }
12
+ end
13
+
14
+ def role_names
15
+ @name_to_role_mapping.keys
16
+ end
17
+
18
+ def pool_configs(role = nil)
19
+ if role
20
+ @name_to_role_mapping[role].values
21
+ else
22
+ @name_to_role_mapping.flat_map { |_, shard_map| shard_map.values }
23
+ end
24
+ end
25
+
26
+ def remove_role(role)
27
+ @name_to_role_mapping.delete(role)
28
+ end
29
+
30
+ def remove_pool_config(role, shard)
31
+ @name_to_role_mapping[role].delete(shard)
32
+ end
33
+
34
+ def get_pool_config(role, shard)
35
+ @name_to_role_mapping[role][shard]
36
+ end
37
+
38
+ def set_pool_config(role, shard, pool_config)
39
+ if pool_config
40
+ @name_to_role_mapping[role][shard] = pool_config
41
+ else
42
+ 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
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -21,7 +21,30 @@ module ActiveRecord
21
21
  alias :array? :array
22
22
 
23
23
  def sql_type
24
- super.sub(/\[\]\z/, "")
24
+ super.delete_suffix("[]")
25
+ end
26
+
27
+ def init_with(coder)
28
+ @serial = coder["serial"]
29
+ super
30
+ end
31
+
32
+ def encode_with(coder)
33
+ coder["serial"] = @serial
34
+ super
35
+ end
36
+
37
+ def ==(other)
38
+ other.is_a?(Column) &&
39
+ super &&
40
+ serial? == other.serial?
41
+ end
42
+ alias :eql? :==
43
+
44
+ def hash
45
+ Column.hash ^
46
+ super.hash ^
47
+ serial?.hash
25
48
  end
26
49
  end
27
50
  end