activerecord 6.0.1 → 6.1.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1363 -647
  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 +26 -15
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +55 -37
  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 +38 -13
  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 +73 -42
  28. data/lib/active_record/associations/preloader/association.rb +49 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +12 -7
  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 +119 -12
  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 +56 -41
  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 +24 -3
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +190 -136
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -38
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -9
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -35
  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 +145 -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 +63 -77
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +136 -111
  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 +20 -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 +30 -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 +80 -66
  104. data/lib/active_record/connection_adapters/schema_cache.rb +130 -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 +57 -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 +218 -87
  114. data/lib/active_record/core.rb +276 -68
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -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 +125 -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 +3 -3
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +42 -9
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +18 -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 +6 -2
  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 +75 -21
  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 +117 -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 +45 -16
  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 +341 -188
  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 -83
  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 +9 -4
  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 +87 -20
  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 +25 -72
  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 +5 -9
  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 +30 -29
  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
@@ -20,12 +20,23 @@ module ActiveRecord
20
20
  end
21
21
 
22
22
  READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
- :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc, :with
23
+ :desc, :describe, :set, :show, :use
24
24
  ) # :nodoc:
25
25
  private_constant :READ_QUERY
26
26
 
27
27
  def write_query?(sql) # :nodoc:
28
28
  !READ_QUERY.match?(sql)
29
+ rescue ArgumentError # Invalid encoding
30
+ !READ_QUERY.match?(sql.b)
31
+ end
32
+
33
+ def explain(arel, binds = [])
34
+ sql = "EXPLAIN #{to_sql(arel, binds)}"
35
+ start = Concurrent.monotonic_time
36
+ result = exec_query(sql, "EXPLAIN", binds)
37
+ elapsed = Concurrent.monotonic_time - start
38
+
39
+ MySQL::ExplainPrettyPrinter.new.pp(result, elapsed)
29
40
  end
30
41
 
31
42
  # Executes the SQL statement in the context of this connection.
@@ -45,17 +56,17 @@ module ActiveRecord
45
56
  if without_prepared_statement?(binds)
46
57
  execute_and_free(sql, name) do |result|
47
58
  if result
48
- ActiveRecord::Result.new(result.fields, result.to_a)
59
+ build_result(columns: result.fields, rows: result.to_a)
49
60
  else
50
- ActiveRecord::Result.new([], [])
61
+ build_result(columns: [], rows: [])
51
62
  end
52
63
  end
53
64
  else
54
65
  exec_stmt_and_free(sql, name, binds, cache_stmt: prepare) do |_, result|
55
66
  if result
56
- ActiveRecord::Result.new(result.fields, result.to_a)
67
+ build_result(columns: result.fields, rows: result.to_a)
57
68
  else
58
- ActiveRecord::Result.new([], [])
69
+ build_result(columns: [], rows: [])
59
70
  end
60
71
  end
61
72
  end
@@ -73,8 +84,10 @@ module ActiveRecord
73
84
  alias :exec_update :exec_delete
74
85
 
75
86
  private
76
- def execute_batch(sql, name = nil)
77
- super
87
+ def execute_batch(statements, name = nil)
88
+ combine_multi_statements(statements).each do |statement|
89
+ execute(statement, name)
90
+ end
78
91
  @connection.abandon_results!
79
92
  end
80
93
 
@@ -86,47 +99,27 @@ module ActiveRecord
86
99
  @connection.last_id
87
100
  end
88
101
 
89
- def supports_set_server_option?
90
- @connection.respond_to?(:set_server_option)
91
- end
92
-
93
- def build_truncate_statements(*table_names)
94
- if table_names.size == 1
95
- super.first
96
- else
97
- super
98
- end
99
- end
102
+ def multi_statements_enabled?
103
+ flags = @config[:flags]
100
104
 
101
- def multi_statements_enabled?(flags)
102
105
  if flags.is_a?(Array)
103
106
  flags.include?("MULTI_STATEMENTS")
104
107
  else
105
- (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
108
+ flags.anybits?(Mysql2::Client::MULTI_STATEMENTS)
106
109
  end
107
110
  end
108
111
 
109
112
  def with_multi_statements
110
- previous_flags = @config[:flags]
113
+ multi_statements_was = multi_statements_enabled?
111
114
 
112
- unless multi_statements_enabled?(previous_flags)
113
- if supports_set_server_option?
114
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
115
- else
116
- @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
117
- reconnect!
118
- end
115
+ unless multi_statements_was
116
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
119
117
  end
120
118
 
121
119
  yield
122
120
  ensure
123
- unless multi_statements_enabled?(previous_flags)
124
- if supports_set_server_option?
125
- @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
126
- else
127
- @config[:flags] = previous_flags
128
- reconnect!
129
- end
121
+ unless multi_statements_was
122
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
130
123
  end
131
124
  end
132
125
 
@@ -163,6 +156,7 @@ module ActiveRecord
163
156
  end
164
157
 
165
158
  materialize_transactions
159
+ mark_transaction_written_if_write(sql)
166
160
 
167
161
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
168
162
  # 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)
@@ -167,6 +167,9 @@ module ActiveRecord
167
167
  elsif type_metadata.extra == "DEFAULT_GENERATED"
168
168
  default = +"(#{default})" unless default.start_with?("(")
169
169
  default, default_function = nil, default
170
+ elsif type_metadata.type == :text && default
171
+ # strip and unescape quotes
172
+ default = default[1...-1].gsub("\\'", "'")
170
173
  end
171
174
 
172
175
  MySQL::Column.new(
@@ -196,17 +199,21 @@ module ActiveRecord
196
199
  end
197
200
 
198
201
  def add_options_for_index_columns(quoted_columns, **options)
199
- quoted_columns = add_index_length(quoted_columns, options)
202
+ quoted_columns = add_index_length(quoted_columns, **options)
200
203
  super
201
204
  end
202
205
 
203
206
  def data_source_sql(name = nil, type: nil)
204
207
  scope = quoted_scope(name, type: type)
205
208
 
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]
209
+ sql = +"SELECT table_name FROM (SELECT table_name, table_type FROM information_schema.tables "
210
+ sql << " WHERE table_schema = #{scope[:schema]}) _subquery"
211
+ if scope[:type] || scope[:name]
212
+ conditions = []
213
+ conditions << "_subquery.table_type = #{scope[:type]}" if scope[:type]
214
+ conditions << "_subquery.table_name = #{scope[:name]}" if scope[:name]
215
+ sql << " WHERE #{conditions.join(" AND ")}"
216
+ end
210
217
  sql
211
218
  end
212
219
 
@@ -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