activerecord 5.2.4.5 → 6.0.0.beta1

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 (241) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +299 -739
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +2 -1
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/associations.rb +16 -12
  9. data/lib/active_record/associations/association.rb +35 -19
  10. data/lib/active_record/associations/association_scope.rb +4 -6
  11. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +14 -50
  14. data/lib/active_record/associations/builder/collection_association.rb +3 -3
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  16. data/lib/active_record/associations/collection_association.rb +11 -25
  17. data/lib/active_record/associations/collection_proxy.rb +32 -6
  18. data/lib/active_record/associations/foreign_association.rb +7 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +25 -18
  21. data/lib/active_record/associations/has_one_association.rb +28 -30
  22. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  23. data/lib/active_record/associations/join_dependency.rb +15 -20
  24. data/lib/active_record/associations/join_dependency/join_association.rb +11 -26
  25. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  26. data/lib/active_record/associations/preloader.rb +32 -29
  27. data/lib/active_record/associations/preloader/association.rb +1 -2
  28. data/lib/active_record/associations/singular_association.rb +2 -16
  29. data/lib/active_record/attribute_assignment.rb +7 -10
  30. data/lib/active_record/attribute_methods.rb +34 -56
  31. data/lib/active_record/attribute_methods/dirty.rb +64 -26
  32. data/lib/active_record/attribute_methods/primary_key.rb +8 -7
  33. data/lib/active_record/attribute_methods/read.rb +16 -48
  34. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  36. data/lib/active_record/attribute_methods/write.rb +15 -16
  37. data/lib/active_record/autosave_association.rb +7 -21
  38. data/lib/active_record/base.rb +2 -2
  39. data/lib/active_record/callbacks.rb +3 -17
  40. data/lib/active_record/collection_cache_key.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +13 -36
  42. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  43. data/lib/active_record/connection_adapters/abstract/database_statements.rb +25 -84
  44. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -14
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +5 -11
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +15 -11
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +30 -13
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +0 -2
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -27
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +81 -52
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +95 -31
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +65 -90
  53. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  54. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +5 -9
  55. data/lib/active_record/connection_adapters/mysql/database_statements.rb +29 -7
  56. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  57. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  58. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +65 -10
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +8 -4
  60. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +16 -1
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  64. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  65. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  66. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  67. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  68. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  69. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  70. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +11 -36
  71. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +9 -2
  72. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +38 -20
  73. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +2 -1
  74. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -56
  75. data/lib/active_record/connection_adapters/schema_cache.rb +5 -0
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -5
  77. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -9
  78. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +95 -62
  79. data/lib/active_record/connection_handling.rb +132 -26
  80. data/lib/active_record/core.rb +76 -43
  81. data/lib/active_record/counter_cache.rb +4 -29
  82. data/lib/active_record/database_configurations.rb +184 -0
  83. data/lib/active_record/database_configurations/database_config.rb +37 -0
  84. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  85. data/lib/active_record/database_configurations/url_config.rb +74 -0
  86. data/lib/active_record/enum.rb +22 -7
  87. data/lib/active_record/errors.rb +24 -21
  88. data/lib/active_record/explain.rb +1 -1
  89. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  90. data/lib/active_record/fixture_set/render_context.rb +17 -0
  91. data/lib/active_record/fixture_set/table_row.rb +153 -0
  92. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  93. data/lib/active_record/fixtures.rb +140 -472
  94. data/lib/active_record/gem_version.rb +4 -4
  95. data/lib/active_record/inheritance.rb +12 -2
  96. data/lib/active_record/integration.rb +56 -16
  97. data/lib/active_record/internal_metadata.rb +5 -1
  98. data/lib/active_record/locking/optimistic.rb +2 -2
  99. data/lib/active_record/locking/pessimistic.rb +3 -3
  100. data/lib/active_record/log_subscriber.rb +7 -26
  101. data/lib/active_record/migration.rb +38 -37
  102. data/lib/active_record/migration/command_recorder.rb +35 -5
  103. data/lib/active_record/migration/compatibility.rb +34 -16
  104. data/lib/active_record/model_schema.rb +30 -9
  105. data/lib/active_record/nested_attributes.rb +2 -2
  106. data/lib/active_record/no_touching.rb +7 -0
  107. data/lib/active_record/persistence.rb +18 -7
  108. data/lib/active_record/query_cache.rb +11 -4
  109. data/lib/active_record/querying.rb +19 -11
  110. data/lib/active_record/railtie.rb +71 -42
  111. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  112. data/lib/active_record/railties/controller_runtime.rb +30 -35
  113. data/lib/active_record/railties/databases.rake +94 -43
  114. data/lib/active_record/reflection.rb +60 -44
  115. data/lib/active_record/relation.rb +150 -69
  116. data/lib/active_record/relation/batches.rb +13 -10
  117. data/lib/active_record/relation/calculations.rb +38 -28
  118. data/lib/active_record/relation/delegation.rb +4 -13
  119. data/lib/active_record/relation/finder_methods.rb +12 -25
  120. data/lib/active_record/relation/merger.rb +2 -6
  121. data/lib/active_record/relation/predicate_builder.rb +4 -6
  122. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  123. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  124. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  125. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  126. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  127. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  128. data/lib/active_record/relation/query_attribute.rb +15 -12
  129. data/lib/active_record/relation/query_methods.rb +29 -52
  130. data/lib/active_record/relation/where_clause.rb +4 -0
  131. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  132. data/lib/active_record/result.rb +30 -11
  133. data/lib/active_record/sanitization.rb +2 -39
  134. data/lib/active_record/schema.rb +1 -10
  135. data/lib/active_record/schema_dumper.rb +12 -6
  136. data/lib/active_record/schema_migration.rb +4 -0
  137. data/lib/active_record/scoping.rb +9 -8
  138. data/lib/active_record/scoping/default.rb +10 -3
  139. data/lib/active_record/scoping/named.rb +10 -14
  140. data/lib/active_record/statement_cache.rb +32 -5
  141. data/lib/active_record/store.rb +39 -8
  142. data/lib/active_record/table_metadata.rb +1 -4
  143. data/lib/active_record/tasks/database_tasks.rb +89 -23
  144. data/lib/active_record/tasks/mysql_database_tasks.rb +2 -4
  145. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  146. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  147. data/lib/active_record/test_databases.rb +38 -0
  148. data/lib/active_record/test_fixtures.rb +224 -0
  149. data/lib/active_record/timestamp.rb +4 -6
  150. data/lib/active_record/transactions.rb +3 -22
  151. data/lib/active_record/translation.rb +1 -1
  152. data/lib/active_record/type.rb +3 -4
  153. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  154. data/lib/active_record/type_caster/connection.rb +1 -6
  155. data/lib/active_record/type_caster/map.rb +1 -4
  156. data/lib/active_record/validations/uniqueness.rb +13 -25
  157. data/lib/arel.rb +44 -0
  158. data/lib/arel/alias_predication.rb +9 -0
  159. data/lib/arel/attributes.rb +22 -0
  160. data/lib/arel/attributes/attribute.rb +37 -0
  161. data/lib/arel/collectors/bind.rb +24 -0
  162. data/lib/arel/collectors/composite.rb +31 -0
  163. data/lib/arel/collectors/plain_string.rb +20 -0
  164. data/lib/arel/collectors/sql_string.rb +20 -0
  165. data/lib/arel/collectors/substitute_binds.rb +28 -0
  166. data/lib/arel/crud.rb +42 -0
  167. data/lib/arel/delete_manager.rb +18 -0
  168. data/lib/arel/errors.rb +9 -0
  169. data/lib/arel/expressions.rb +29 -0
  170. data/lib/arel/factory_methods.rb +49 -0
  171. data/lib/arel/insert_manager.rb +49 -0
  172. data/lib/arel/math.rb +45 -0
  173. data/lib/arel/nodes.rb +67 -0
  174. data/lib/arel/nodes/and.rb +32 -0
  175. data/lib/arel/nodes/ascending.rb +23 -0
  176. data/lib/arel/nodes/binary.rb +52 -0
  177. data/lib/arel/nodes/bind_param.rb +36 -0
  178. data/lib/arel/nodes/case.rb +55 -0
  179. data/lib/arel/nodes/casted.rb +50 -0
  180. data/lib/arel/nodes/count.rb +12 -0
  181. data/lib/arel/nodes/delete_statement.rb +45 -0
  182. data/lib/arel/nodes/descending.rb +23 -0
  183. data/lib/arel/nodes/equality.rb +18 -0
  184. data/lib/arel/nodes/extract.rb +24 -0
  185. data/lib/arel/nodes/false.rb +16 -0
  186. data/lib/arel/nodes/full_outer_join.rb +8 -0
  187. data/lib/arel/nodes/function.rb +44 -0
  188. data/lib/arel/nodes/grouping.rb +8 -0
  189. data/lib/arel/nodes/in.rb +8 -0
  190. data/lib/arel/nodes/infix_operation.rb +80 -0
  191. data/lib/arel/nodes/inner_join.rb +8 -0
  192. data/lib/arel/nodes/insert_statement.rb +37 -0
  193. data/lib/arel/nodes/join_source.rb +20 -0
  194. data/lib/arel/nodes/matches.rb +18 -0
  195. data/lib/arel/nodes/named_function.rb +23 -0
  196. data/lib/arel/nodes/node.rb +50 -0
  197. data/lib/arel/nodes/node_expression.rb +13 -0
  198. data/lib/arel/nodes/outer_join.rb +8 -0
  199. data/lib/arel/nodes/over.rb +15 -0
  200. data/lib/arel/nodes/regexp.rb +16 -0
  201. data/lib/arel/nodes/right_outer_join.rb +8 -0
  202. data/lib/arel/nodes/select_core.rb +63 -0
  203. data/lib/arel/nodes/select_statement.rb +41 -0
  204. data/lib/arel/nodes/sql_literal.rb +16 -0
  205. data/lib/arel/nodes/string_join.rb +11 -0
  206. data/lib/arel/nodes/table_alias.rb +27 -0
  207. data/lib/arel/nodes/terminal.rb +16 -0
  208. data/lib/arel/nodes/true.rb +16 -0
  209. data/lib/arel/nodes/unary.rb +44 -0
  210. data/lib/arel/nodes/unary_operation.rb +20 -0
  211. data/lib/arel/nodes/unqualified_column.rb +22 -0
  212. data/lib/arel/nodes/update_statement.rb +41 -0
  213. data/lib/arel/nodes/values.rb +16 -0
  214. data/lib/arel/nodes/values_list.rb +24 -0
  215. data/lib/arel/nodes/window.rb +126 -0
  216. data/lib/arel/nodes/with.rb +11 -0
  217. data/lib/arel/order_predications.rb +13 -0
  218. data/lib/arel/predications.rb +257 -0
  219. data/lib/arel/select_manager.rb +271 -0
  220. data/lib/arel/table.rb +110 -0
  221. data/lib/arel/tree_manager.rb +72 -0
  222. data/lib/arel/update_manager.rb +34 -0
  223. data/lib/arel/visitors.rb +20 -0
  224. data/lib/arel/visitors/depth_first.rb +199 -0
  225. data/lib/arel/visitors/dot.rb +292 -0
  226. data/lib/arel/visitors/ibm_db.rb +21 -0
  227. data/lib/arel/visitors/informix.rb +56 -0
  228. data/lib/arel/visitors/mssql.rb +143 -0
  229. data/lib/arel/visitors/mysql.rb +83 -0
  230. data/lib/arel/visitors/oracle.rb +159 -0
  231. data/lib/arel/visitors/oracle12.rb +67 -0
  232. data/lib/arel/visitors/postgresql.rb +116 -0
  233. data/lib/arel/visitors/sqlite.rb +39 -0
  234. data/lib/arel/visitors/to_sql.rb +913 -0
  235. data/lib/arel/visitors/visitor.rb +42 -0
  236. data/lib/arel/visitors/where_sql.rb +23 -0
  237. data/lib/arel/window_predications.rb +9 -0
  238. data/lib/rails/generators/active_record/migration.rb +14 -1
  239. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  240. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  241. metadata +104 -26
@@ -11,8 +11,6 @@ require "active_record/connection_adapters/mysql/schema_dumper"
11
11
  require "active_record/connection_adapters/mysql/schema_statements"
12
12
  require "active_record/connection_adapters/mysql/type_metadata"
13
13
 
14
- require "active_support/core_ext/string/strip"
15
-
16
14
  module ActiveRecord
17
15
  module ConnectionAdapters
18
16
  class AbstractMysqlAdapter < AbstractAdapter
@@ -45,19 +43,17 @@ module ActiveRecord
45
43
  }
46
44
 
47
45
  class StatementPool < ConnectionAdapters::StatementPool # :nodoc:
48
- private def dealloc(stmt)
49
- stmt[:stmt].close
50
- end
46
+ private
47
+
48
+ def dealloc(stmt)
49
+ stmt.close
50
+ end
51
51
  end
52
52
 
53
53
  def initialize(connection, logger, connection_options, config)
54
54
  super(connection, logger, config)
55
55
 
56
56
  @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit]))
57
-
58
- if version < "5.1.10"
59
- raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10."
60
- end
61
57
  end
62
58
 
63
59
  def version #:nodoc:
@@ -76,6 +72,10 @@ module ActiveRecord
76
72
  !mariadb? && version >= "8.0.1"
77
73
  end
78
74
 
75
+ def supports_expression_index?
76
+ !mariadb? && version >= "8.0.13"
77
+ end
78
+
79
79
  def supports_transaction_isolation?
80
80
  true
81
81
  end
@@ -97,19 +97,11 @@ module ActiveRecord
97
97
  end
98
98
 
99
99
  def supports_datetime_with_precision?
100
- if mariadb?
101
- version >= "5.3.0"
102
- else
103
- version >= "5.6.4"
104
- end
100
+ mariadb? || version >= "5.6.4"
105
101
  end
106
102
 
107
103
  def supports_virtual_columns?
108
- if mariadb?
109
- version >= "5.2.0"
110
- else
111
- version >= "5.7.5"
112
- end
104
+ mariadb? || version >= "5.7.5"
113
105
  end
114
106
 
115
107
  def supports_advisory_locks?
@@ -129,7 +121,7 @@ module ActiveRecord
129
121
  end
130
122
 
131
123
  def index_algorithms
132
- { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup }
124
+ { default: +"ALGORITHM = DEFAULT", copy: +"ALGORITHM = COPY", inplace: +"ALGORITHM = INPLACE" }
133
125
  end
134
126
 
135
127
  # HELPER METHODS ===========================================
@@ -182,6 +174,8 @@ module ActiveRecord
182
174
 
183
175
  # Executes the SQL statement in the context of this connection.
184
176
  def execute(sql, name = nil)
177
+ materialize_transactions
178
+
185
179
  log(sql, name) do
186
180
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
187
181
  @connection.query(sql)
@@ -213,19 +207,7 @@ module ActiveRecord
213
207
  execute "ROLLBACK"
214
208
  end
215
209
 
216
- # In the simple case, MySQL allows us to place JOINs directly into the UPDATE
217
- # query. However, this does not allow for LIMIT, OFFSET and ORDER. To support
218
- # these, we must use a subquery.
219
- def join_to_update(update, select, key) # :nodoc:
220
- if select.limit || select.offset || select.orders.any?
221
- super
222
- else
223
- update.table select.source
224
- update.wheres = select.constraints
225
- end
226
- end
227
-
228
- def empty_insert_statement_value
210
+ def empty_insert_statement_value(primary_key = nil)
229
211
  "VALUES ()"
230
212
  end
231
213
 
@@ -241,7 +223,7 @@ module ActiveRecord
241
223
  end
242
224
 
243
225
  # Create a new MySQL database with optional <tt>:charset</tt> and <tt>:collation</tt>.
244
- # Charset defaults to utf8.
226
+ # Charset defaults to utf8mb4.
245
227
  #
246
228
  # Example:
247
229
  # create_database 'charset_test', charset: 'latin1', collation: 'latin1_bin'
@@ -250,8 +232,12 @@ module ActiveRecord
250
232
  def create_database(name, options = {})
251
233
  if options[:collation]
252
234
  execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}"
235
+ elsif options[:charset]
236
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}"
237
+ elsif row_format_dynamic_by_default?
238
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`"
253
239
  else
254
- execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
240
+ raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns."
255
241
  end
256
242
  end
257
243
 
@@ -284,7 +270,7 @@ module ActiveRecord
284
270
  def table_comment(table_name) # :nodoc:
285
271
  scope = quoted_scope(table_name)
286
272
 
287
- query_value(<<-SQL.strip_heredoc, "SCHEMA").presence
273
+ query_value(<<~SQL, "SCHEMA").presence
288
274
  SELECT table_comment
289
275
  FROM information_schema.tables
290
276
  WHERE table_schema = #{scope[:schema]}
@@ -378,7 +364,7 @@ module ActiveRecord
378
364
 
379
365
  def add_index(table_name, column_name, options = {}) #:nodoc:
380
366
  index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
381
- sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup
367
+ sql = +"CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
382
368
  execute add_sql_comment!(sql, comment)
383
369
  end
384
370
 
@@ -392,7 +378,7 @@ module ActiveRecord
392
378
 
393
379
  scope = quoted_scope(table_name)
394
380
 
395
- fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA")
381
+ fk_info = exec_query(<<~SQL, "SCHEMA")
396
382
  SELECT fk.referenced_table_name AS 'to_table',
397
383
  fk.referenced_column_name AS 'primary_key',
398
384
  fk.column_name AS 'column',
@@ -480,7 +466,7 @@ module ActiveRecord
480
466
 
481
467
  scope = quoted_scope(table_name)
482
468
 
483
- query_values(<<-SQL.strip_heredoc, "SCHEMA")
469
+ query_values(<<~SQL, "SCHEMA")
484
470
  SELECT column_name
485
471
  FROM information_schema.key_column_usage
486
472
  WHERE constraint_name = 'PRIMARY'
@@ -490,9 +476,11 @@ module ActiveRecord
490
476
  SQL
491
477
  end
492
478
 
493
- def case_sensitive_comparison(table, attribute, column, value) # :nodoc:
479
+ def case_sensitive_comparison(attribute, value) # :nodoc:
480
+ column = column_for_attribute(attribute)
481
+
494
482
  if column.collation && !column.case_sensitive?
495
- table[attribute].eq(Arel::Nodes::Bin.new(value))
483
+ attribute.eq(Arel::Nodes::Bin.new(value))
496
484
  else
497
485
  super
498
486
  end
@@ -533,6 +521,12 @@ module ActiveRecord
533
521
  end
534
522
 
535
523
  private
524
+ def check_version
525
+ if version < "5.5.8"
526
+ raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.5.8."
527
+ end
528
+ end
529
+
536
530
  def combine_multi_statements(total_sql)
537
531
  total_sql.each_with_object([]) do |sql, total_sql_chunks|
538
532
  previous_packet = total_sql_chunks.last
@@ -587,13 +581,13 @@ module ActiveRecord
587
581
  m.alias_type %r(bit)i, "binary"
588
582
 
589
583
  m.register_type(%r(enum)i) do |sql_type|
590
- limit = sql_type[/^enum\((.+)\)/i, 1]
584
+ limit = sql_type[/^enum\s*\((.+)\)/i, 1]
591
585
  .split(",").map { |enum| enum.strip.length - 2 }.max
592
586
  MysqlString.new(limit: limit)
593
587
  end
594
588
 
595
589
  m.register_type(%r(^set)i) do |sql_type|
596
- limit = sql_type[/^set\((.+)\)/i, 1]
590
+ limit = sql_type[/^set\s*\((.+)\)/i, 1]
597
591
  .split(",").map { |set| set.strip.length - 1 }.sum - 1
598
592
  MysqlString.new(limit: limit)
599
593
  end
@@ -620,7 +614,10 @@ module ActiveRecord
620
614
  # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
621
615
  ER_DUP_ENTRY = 1062
622
616
  ER_NOT_NULL_VIOLATION = 1048
617
+ ER_NO_REFERENCED_ROW = 1216
618
+ ER_ROW_IS_REFERENCED = 1217
623
619
  ER_DO_NOT_HAVE_DEFAULT = 1364
620
+ ER_ROW_IS_REFERENCED_2 = 1451
624
621
  ER_NO_REFERENCED_ROW_2 = 1452
625
622
  ER_DATA_TOO_LONG = 1406
626
623
  ER_OUT_OF_RANGE = 1264
@@ -631,34 +628,34 @@ module ActiveRecord
631
628
  ER_QUERY_INTERRUPTED = 1317
632
629
  ER_QUERY_TIMEOUT = 3024
633
630
 
634
- def translate_exception(exception, message)
631
+ def translate_exception(exception, message:, sql:, binds:)
635
632
  case error_number(exception)
636
633
  when ER_DUP_ENTRY
637
- RecordNotUnique.new(message)
638
- when ER_NO_REFERENCED_ROW_2
639
- InvalidForeignKey.new(message)
634
+ RecordNotUnique.new(message, sql: sql, binds: binds)
635
+ when ER_NO_REFERENCED_ROW, ER_ROW_IS_REFERENCED, ER_ROW_IS_REFERENCED_2, ER_NO_REFERENCED_ROW_2
636
+ InvalidForeignKey.new(message, sql: sql, binds: binds)
640
637
  when ER_CANNOT_ADD_FOREIGN
641
- mismatched_foreign_key(message)
638
+ mismatched_foreign_key(message, sql: sql, binds: binds)
642
639
  when ER_CANNOT_CREATE_TABLE
643
640
  if message.include?("errno: 150")
644
- mismatched_foreign_key(message)
641
+ mismatched_foreign_key(message, sql: sql, binds: binds)
645
642
  else
646
643
  super
647
644
  end
648
645
  when ER_DATA_TOO_LONG
649
- ValueTooLong.new(message)
646
+ ValueTooLong.new(message, sql: sql, binds: binds)
650
647
  when ER_OUT_OF_RANGE
651
- RangeError.new(message)
648
+ RangeError.new(message, sql: sql, binds: binds)
652
649
  when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
653
- NotNullViolation.new(message)
650
+ NotNullViolation.new(message, sql: sql, binds: binds)
654
651
  when ER_LOCK_DEADLOCK
655
- Deadlocked.new(message)
652
+ Deadlocked.new(message, sql: sql, binds: binds)
656
653
  when ER_LOCK_WAIT_TIMEOUT
657
- LockWaitTimeout.new(message)
654
+ LockWaitTimeout.new(message, sql: sql, binds: binds)
658
655
  when ER_QUERY_TIMEOUT
659
- StatementTimeout.new(message)
656
+ StatementTimeout.new(message, sql: sql, binds: binds)
660
657
  when ER_QUERY_INTERRUPTED
661
- QueryCanceled.new(message)
658
+ QueryCanceled.new(message, sql: sql, binds: binds)
662
659
  else
663
660
  super
664
661
  end
@@ -718,20 +715,6 @@ module ActiveRecord
718
715
  [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)]
719
716
  end
720
717
 
721
- # MySQL is too stupid to create a temporary table for use subquery, so we have
722
- # to give it some prompting in the form of a subsubquery. Ugh!
723
- def subquery_for(key, select)
724
- subselect = select.clone
725
- subselect.projections = [key]
726
-
727
- # Materialize subquery by adding distinct
728
- # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
729
- subselect.distinct unless select.limit || select.offset || select.orders.any?
730
-
731
- key_name = quote_column_name(key.name)
732
- Arel::SelectManager.new(subselect.as("__active_record_temp")).project(Arel.sql(key_name))
733
- end
734
-
735
718
  def supports_rename_index?
736
719
  mariadb? ? false : version >= "5.7.6"
737
720
  end
@@ -770,7 +753,7 @@ module ActiveRecord
770
753
  # https://dev.mysql.com/doc/refman/5.7/en/set-names.html
771
754
  # (trailing comma because variable_assignments will always have content)
772
755
  if @config[:encoding]
773
- encoding = "NAMES #{@config[:encoding]}".dup
756
+ encoding = +"NAMES #{@config[:encoding]}"
774
757
  encoding << " COLLATE #{@config[:collation]}" if @config[:collation]
775
758
  encoding << ", "
776
759
  end
@@ -803,26 +786,18 @@ module ActiveRecord
803
786
  Arel::Visitors::MySQL.new(self)
804
787
  end
805
788
 
806
- def mismatched_foreign_key(message)
807
- match = %r/
808
- (?:CREATE|ALTER)\s+TABLE\s*(?:`?\w+`?\.)?`?(?<table>\w+)`?.+?
809
- FOREIGN\s+KEY\s*\(`?(?<foreign_key>\w+)`?\)\s*
810
- REFERENCES\s*(`?(?<target_table>\w+)`?)\s*\(`?(?<primary_key>\w+)`?\)
811
- /xmi.match(message)
812
-
813
- options = {
789
+ def mismatched_foreign_key(message, sql:, binds:)
790
+ parts = sql.scan(/`(\w+)`[ $)]/).flatten
791
+ MismatchedForeignKey.new(
792
+ self,
814
793
  message: message,
815
- }
816
-
817
- if match
818
- options[:table] = match[:table]
819
- options[:foreign_key] = match[:foreign_key]
820
- options[:target_table] = match[:target_table]
821
- options[:primary_key] = match[:primary_key]
822
- options[:primary_key_column] = column_for(match[:target_table], match[:primary_key])
823
- end
824
-
825
- MismatchedForeignKey.new(options)
794
+ sql: sql,
795
+ binds: binds,
796
+ table: parts[0],
797
+ foreign_key: parts[1],
798
+ target_table: parts[2],
799
+ primary_key: parts[3],
800
+ )
826
801
  end
827
802
 
828
803
  def integer_to_sql(limit) # :nodoc:
@@ -57,9 +57,7 @@ module ActiveRecord
57
57
 
58
58
  private
59
59
 
60
- def uri
61
- @uri
62
- end
60
+ attr_reader :uri
63
61
 
64
62
  def uri_parser
65
63
  @uri_parser ||= URI::Parser.new
@@ -116,8 +114,7 @@ module ActiveRecord
116
114
  class Resolver # :nodoc:
117
115
  attr_reader :configurations
118
116
 
119
- # Accepts a hash two layers deep, keys on the first layer represent
120
- # environments such as "production". Keys must be strings.
117
+ # Accepts a list of db config objects.
121
118
  def initialize(configurations)
122
119
  @configurations = configurations
123
120
  end
@@ -138,34 +135,14 @@ module ActiveRecord
138
135
  # Resolver.new(configurations).resolve(:production)
139
136
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
140
137
  #
141
- def resolve(config)
142
- if config
143
- resolve_connection config
144
- elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
145
- resolve_symbol_connection env.to_sym
138
+ def resolve(config_or_env, pool_name = nil)
139
+ if config_or_env
140
+ resolve_connection config_or_env, pool_name
146
141
  else
147
142
  raise AdapterNotSpecified
148
143
  end
149
144
  end
150
145
 
151
- # Expands each key in @configurations hash into fully resolved hash
152
- def resolve_all
153
- config = configurations.dup
154
-
155
- if env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
156
- env_config = config[env] if config[env].is_a?(Hash) && !(config[env].key?("adapter") || config[env].key?("url"))
157
- end
158
-
159
- config.reject! { |k, v| v.is_a?(Hash) && !(v.key?("adapter") || v.key?("url")) }
160
- config.merge! env_config if env_config
161
-
162
- config.each do |key, value|
163
- config[key] = resolve(value) if value
164
- end
165
-
166
- config
167
- end
168
-
169
146
  # Returns an instance of ConnectionSpecification for a given adapter.
170
147
  # Accepts a hash one layer deep that contains all connection information.
171
148
  #
@@ -179,7 +156,9 @@ module ActiveRecord
179
156
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
180
157
  #
181
158
  def spec(config)
182
- spec = resolve(config).symbolize_keys
159
+ pool_name = config if config.is_a?(Symbol)
160
+
161
+ spec = resolve(config, pool_name).symbolize_keys
183
162
 
184
163
  raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
185
164
 
@@ -214,7 +193,6 @@ module ActiveRecord
214
193
  end
215
194
 
216
195
  private
217
-
218
196
  # Returns fully resolved connection, accepts hash, string or symbol.
219
197
  # Always returns a hash.
220
198
  #
@@ -235,32 +213,64 @@ module ActiveRecord
235
213
  # Resolver.new({}).resolve_connection("postgresql://localhost/foo")
236
214
  # # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
237
215
  #
238
- def resolve_connection(spec)
239
- case spec
216
+ def resolve_connection(config_or_env, pool_name = nil)
217
+ case config_or_env
240
218
  when Symbol
241
- resolve_symbol_connection spec
219
+ resolve_symbol_connection config_or_env, pool_name
242
220
  when String
243
- resolve_url_connection spec
221
+ resolve_url_connection config_or_env
244
222
  when Hash
245
- resolve_hash_connection spec
223
+ resolve_hash_connection config_or_env
224
+ else
225
+ resolve_connection config_or_env
246
226
  end
247
227
  end
248
228
 
249
- # Takes the environment such as +:production+ or +:development+.
229
+ # Takes the environment such as +:production+ or +:development+ and a
230
+ # pool name the corresponds to the name given by the connection pool
231
+ # to the connection. That pool name is merged into the hash with the
232
+ # name key.
233
+ #
250
234
  # This requires that the @configurations was initialized with a key that
251
235
  # matches.
252
236
  #
253
- # Resolver.new("production" => {}).resolve_symbol_connection(:production)
254
- # # => {}
237
+ # configurations = #<ActiveRecord::DatabaseConfigurations:0x00007fd9fdace3e0
238
+ # @configurations=[
239
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd9fdace250
240
+ # @env_name="production", @spec_name="primary", @config={"database"=>"my_db"}>
241
+ # ]>
255
242
  #
256
- def resolve_symbol_connection(spec)
257
- if config = configurations[spec.to_s]
258
- resolve_connection(config).merge("name" => spec.to_s)
243
+ # Resolver.new(configurations).resolve_symbol_connection(:production, "primary")
244
+ # # => { "database" => "my_db" }
245
+ def resolve_symbol_connection(env_name, pool_name)
246
+ db_config = configurations.find_db_config(env_name)
247
+
248
+ if db_config
249
+ resolve_connection(db_config.config).merge("name" => pool_name.to_s)
259
250
  else
260
- raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available: #{configurations.keys.inspect}")
251
+ raise AdapterNotSpecified, <<~MSG
252
+ The `#{env_name}` database is not configured for the `#{ActiveRecord::ConnectionHandling::DEFAULT_ENV.call}` environment.
253
+
254
+ Available databases configurations are:
255
+
256
+ #{build_configuration_sentence}
257
+ MSG
261
258
  end
262
259
  end
263
260
 
261
+ def build_configuration_sentence # :nodoc:
262
+ configs = configurations.configs_for(include_replicas: true)
263
+
264
+ configs.group_by(&:env_name).map do |env, config|
265
+ namespaces = config.map(&:spec_name)
266
+ if namespaces.size > 1
267
+ "#{env}: #{namespaces.join(", ")}"
268
+ else
269
+ env
270
+ end
271
+ end.join("\n")
272
+ end
273
+
264
274
  # Accepts a hash. Expands the "url" key that contains a
265
275
  # URL database connection to a full connection
266
276
  # hash and merges with the rest of the hash.