activerecord 5.2.0 → 5.2.8.1

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 (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +361 -0
  3. data/lib/active_record/association_relation.rb +3 -3
  4. data/lib/active_record/associations/alias_tracker.rb +1 -1
  5. data/lib/active_record/associations/association.rb +25 -10
  6. data/lib/active_record/associations/belongs_to_association.rb +14 -5
  7. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +4 -1
  8. data/lib/active_record/associations/builder/belongs_to.rb +11 -2
  9. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  10. data/lib/active_record/associations/collection_association.rb +19 -15
  11. data/lib/active_record/associations/collection_proxy.rb +8 -34
  12. data/lib/active_record/associations/has_many_association.rb +9 -0
  13. data/lib/active_record/associations/has_many_through_association.rb +29 -12
  14. data/lib/active_record/associations/has_one_association.rb +8 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  16. data/lib/active_record/associations/join_dependency/join_association.rb +39 -24
  17. data/lib/active_record/associations/join_dependency/join_part.rb +7 -0
  18. data/lib/active_record/associations/join_dependency.rb +39 -64
  19. data/lib/active_record/associations/preloader.rb +1 -1
  20. data/lib/active_record/associations/singular_association.rb +4 -10
  21. data/lib/active_record/associations/through_association.rb +1 -1
  22. data/lib/active_record/associations.rb +9 -9
  23. data/lib/active_record/attribute_methods/dirty.rb +15 -10
  24. data/lib/active_record/attribute_methods/read.rb +1 -1
  25. data/lib/active_record/autosave_association.rb +27 -8
  26. data/lib/active_record/callbacks.rb +4 -0
  27. data/lib/active_record/coders/yaml_column.rb +13 -1
  28. data/lib/active_record/collection_cache_key.rb +2 -2
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +36 -11
  30. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  31. data/lib/active_record/connection_adapters/abstract/database_statements.rb +19 -6
  32. data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -3
  33. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -0
  34. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -4
  35. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +6 -15
  36. data/lib/active_record/connection_adapters/abstract/transaction.rb +23 -14
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +3 -1
  38. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +18 -19
  39. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  40. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +11 -2
  41. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -0
  42. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +2 -2
  43. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +11 -1
  44. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  45. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +4 -0
  46. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +36 -0
  47. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +12 -26
  48. data/lib/active_record/connection_adapters/postgresql/utils.rb +1 -1
  49. data/lib/active_record/connection_adapters/postgresql_adapter.rb +9 -1
  50. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +5 -0
  51. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +5 -1
  52. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +3 -6
  53. data/lib/active_record/core.rb +12 -1
  54. data/lib/active_record/counter_cache.rb +17 -13
  55. data/lib/active_record/enum.rb +1 -0
  56. data/lib/active_record/errors.rb +18 -12
  57. data/lib/active_record/gem_version.rb +2 -2
  58. data/lib/active_record/log_subscriber.rb +1 -1
  59. data/lib/active_record/migration/compatibility.rb +15 -15
  60. data/lib/active_record/migration.rb +1 -1
  61. data/lib/active_record/model_schema.rb +1 -1
  62. data/lib/active_record/persistence.rb +6 -5
  63. data/lib/active_record/query_cache.rb +4 -11
  64. data/lib/active_record/querying.rb +1 -1
  65. data/lib/active_record/railtie.rb +19 -3
  66. data/lib/active_record/reflection.rb +10 -14
  67. data/lib/active_record/relation/calculations.rb +16 -12
  68. data/lib/active_record/relation/delegation.rb +30 -0
  69. data/lib/active_record/relation/finder_methods.rb +10 -8
  70. data/lib/active_record/relation/merger.rb +10 -11
  71. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  72. data/lib/active_record/relation/predicate_builder.rb +20 -14
  73. data/lib/active_record/relation/query_attribute.rb +5 -3
  74. data/lib/active_record/relation/query_methods.rb +50 -22
  75. data/lib/active_record/relation/spawn_methods.rb +1 -1
  76. data/lib/active_record/relation.rb +39 -20
  77. data/lib/active_record/scoping/default.rb +2 -2
  78. data/lib/active_record/scoping/named.rb +2 -0
  79. data/lib/active_record/statement_cache.rb +2 -2
  80. data/lib/active_record/tasks/database_tasks.rb +1 -1
  81. data/lib/active_record/timestamp.rb +8 -1
  82. data/lib/active_record/transactions.rb +24 -21
  83. data/lib/active_record/type/serialized.rb +4 -0
  84. metadata +12 -13
@@ -272,7 +272,7 @@ module ActiveRecord
272
272
  # or saved. If +autosave+ is +false+ only new records will be returned,
273
273
  # unless the parent is/was a new record itself.
274
274
  def associated_records_to_validate_or_save(association, new_record, autosave)
275
- if new_record
275
+ if new_record || custom_validation_context?
276
276
  association && association.target
277
277
  elsif autosave
278
278
  association.target.find_all(&:changed_for_autosave?)
@@ -304,7 +304,7 @@ module ActiveRecord
304
304
  def validate_single_association(reflection)
305
305
  association = association_instance_get(reflection.name)
306
306
  record = association && association.reader
307
- association_valid?(reflection, record) if record
307
+ association_valid?(reflection, record) if record && (record.changed_for_autosave? || custom_validation_context?)
308
308
  end
309
309
 
310
310
  # Validate the associated records if <tt>:validate</tt> or
@@ -324,7 +324,7 @@ module ActiveRecord
324
324
  def association_valid?(reflection, record, index = nil)
325
325
  return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
326
326
 
327
- context = validation_context unless [:create, :update].include?(validation_context)
327
+ context = validation_context if custom_validation_context?
328
328
 
329
329
  unless valid = record.valid?(context)
330
330
  if reflection.options[:autosave]
@@ -382,10 +382,14 @@ module ActiveRecord
382
382
  if association = association_instance_get(reflection.name)
383
383
  autosave = reflection.options[:autosave]
384
384
 
385
+ # By saving the instance variable in a local variable,
386
+ # we make the whole callback re-entrant.
387
+ new_record_before_save = @new_record_before_save
388
+
385
389
  # reconstruct the scope now that we know the owner's id
386
390
  association.reset_scope
387
391
 
388
- if records = associated_records_to_validate_or_save(association, @new_record_before_save, autosave)
392
+ if records = associated_records_to_validate_or_save(association, new_record_before_save, autosave)
389
393
  if autosave
390
394
  records_to_destroy = records.select(&:marked_for_destruction?)
391
395
  records_to_destroy.each { |record| association.destroy(record) }
@@ -397,11 +401,16 @@ module ActiveRecord
397
401
 
398
402
  saved = true
399
403
 
400
- if autosave != false && (@new_record_before_save || record.new_record?)
404
+ if autosave != false && (new_record_before_save || record.new_record?)
401
405
  if autosave
402
406
  saved = association.insert_record(record, false)
403
- else
404
- association.insert_record(record) unless reflection.nested?
407
+ elsif !reflection.nested?
408
+ association_saved = association.insert_record(record)
409
+
410
+ if reflection.validate?
411
+ errors.add(reflection.name) unless association_saved
412
+ saved = association_saved
413
+ end
405
414
  end
406
415
  elsif autosave
407
416
  saved = record.save(validate: false)
@@ -452,10 +461,16 @@ module ActiveRecord
452
461
  # If the record is new or it has changed, returns true.
453
462
  def record_changed?(reflection, record, key)
454
463
  record.new_record? ||
455
- (record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key) ||
464
+ association_foreign_key_changed?(reflection, record, key) ||
456
465
  record.will_save_change_to_attribute?(reflection.foreign_key)
457
466
  end
458
467
 
468
+ def association_foreign_key_changed?(reflection, record, key)
469
+ return false if reflection.through_reflection?
470
+
471
+ record.has_attribute?(reflection.foreign_key) && record[reflection.foreign_key] != key
472
+ end
473
+
459
474
  # Saves the associated record if it's new or <tt>:autosave</tt> is enabled.
460
475
  #
461
476
  # In addition, it will destroy the association if it was marked for destruction.
@@ -484,6 +499,10 @@ module ActiveRecord
484
499
  end
485
500
  end
486
501
 
502
+ def custom_validation_context?
503
+ validation_context && [:create, :update].exclude?(validation_context)
504
+ end
505
+
487
506
  def _ensure_no_duplicate_errors
488
507
  errors.messages.each_key do |attribute|
489
508
  errors[attribute].uniq!
@@ -332,6 +332,10 @@ module ActiveRecord
332
332
  _run_touch_callbacks { super }
333
333
  end
334
334
 
335
+ def increment!(attribute, by = 1, touch: nil) # :nodoc:
336
+ touch ? _run_touch_callbacks { super } : super
337
+ end
338
+
335
339
  private
336
340
 
337
341
  def create_or_update(*)
@@ -23,7 +23,7 @@ module ActiveRecord
23
23
  def load(yaml)
24
24
  return object_class.new if object_class != Object && yaml.nil?
25
25
  return yaml unless yaml.is_a?(String) && /^---/.match?(yaml)
26
- obj = YAML.load(yaml)
26
+ obj = yaml_load(yaml)
27
27
 
28
28
  assert_valid_value(obj, action: "load")
29
29
  obj ||= object_class.new if object_class != Object
@@ -45,6 +45,18 @@ module ActiveRecord
45
45
  rescue ArgumentError
46
46
  raise ArgumentError, "Cannot serialize #{object_class}. Classes passed to `serialize` must have a 0 argument constructor."
47
47
  end
48
+
49
+ def yaml_load(payload)
50
+ if !ActiveRecord::Base.use_yaml_unsafe_load
51
+ YAML.safe_load(payload, permitted_classes: ActiveRecord::Base.yaml_column_permitted_classes, aliases: true)
52
+ else
53
+ if YAML.respond_to?(:unsafe_load)
54
+ YAML.unsafe_load(payload)
55
+ else
56
+ YAML.load(payload)
57
+ end
58
+ end
59
+ end
48
60
  end
49
61
  end
50
62
  end
@@ -20,9 +20,9 @@ module ActiveRecord
20
20
  select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp"
21
21
 
22
22
  if collection.has_limit_or_offset?
23
- query = collection.select(column)
23
+ query = collection.select("#{column} AS collection_cache_key_timestamp")
24
24
  subquery_alias = "subquery_for_cache_key"
25
- subquery_column = "#{subquery_alias}.#{timestamp_column}"
25
+ subquery_column = "#{subquery_alias}.collection_cache_key_timestamp"
26
26
  subquery = query.arel.as(subquery_alias)
27
27
  arel = Arel::SelectManager.new(subquery).project(select_values % subquery_column)
28
28
  else
@@ -188,7 +188,9 @@ module ActiveRecord
188
188
  t0 = Time.now
189
189
  elapsed = 0
190
190
  loop do
191
- @cond.wait(timeout - elapsed)
191
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
192
+ @cond.wait(timeout - elapsed)
193
+ end
192
194
 
193
195
  return remove if any?
194
196
 
@@ -308,7 +310,7 @@ module ActiveRecord
308
310
  include QueryCache::ConnectionPoolConfiguration
309
311
 
310
312
  attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
311
- attr_reader :spec, :connections, :size, :reaper
313
+ attr_reader :spec, :size, :reaper
312
314
 
313
315
  # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
314
316
  # object which describes database connection information (e.g. adapter,
@@ -377,7 +379,7 @@ module ActiveRecord
377
379
  # #connection can be called any number of times; the connection is
378
380
  # held in a cache keyed by a thread.
379
381
  def connection
380
- @thread_cached_conns[connection_cache_key(@lock_thread || Thread.current)] ||= checkout
382
+ @thread_cached_conns[connection_cache_key(current_thread)] ||= checkout
381
383
  end
382
384
 
383
385
  # Returns true if there is an open connection being used for the current thread.
@@ -386,7 +388,7 @@ module ActiveRecord
386
388
  # #connection or #with_connection methods. Connections obtained through
387
389
  # #checkout will not be detected by #active_connection?
388
390
  def active_connection?
389
- @thread_cached_conns[connection_cache_key(Thread.current)]
391
+ @thread_cached_conns[connection_cache_key(current_thread)]
390
392
  end
391
393
 
392
394
  # Signal that the thread is finished with the current connection.
@@ -421,6 +423,21 @@ module ActiveRecord
421
423
  synchronize { @connections.any? }
422
424
  end
423
425
 
426
+ # Returns an array containing the connections currently in the pool.
427
+ # Access to the array does not require synchronization on the pool because
428
+ # the array is newly created and not retained by the pool.
429
+ #
430
+ # However; this method bypasses the ConnectionPool's thread-safe connection
431
+ # access pattern. A returned connection may be owned by another thread,
432
+ # unowned, or by happen-stance owned by the calling thread.
433
+ #
434
+ # Calling methods on a connection without ownership is subject to the
435
+ # thread-safety guarantees of the underlying method. Many of the methods
436
+ # on connection adapter classes are inherently multi-thread unsafe.
437
+ def connections
438
+ synchronize { @connections.dup }
439
+ end
440
+
424
441
  # Disconnects all connections in the pool, and clears the pool.
425
442
  #
426
443
  # Raises:
@@ -666,6 +683,10 @@ module ActiveRecord
666
683
  thread
667
684
  end
668
685
 
686
+ def current_thread
687
+ @lock_thread || Thread.current
688
+ end
689
+
669
690
  # Take control of all existing connections so a "group" action such as
670
691
  # reload/disconnect can be performed safely. It is no longer enough to
671
692
  # wrap it in +synchronize+ because some pool's actions are allowed
@@ -913,6 +934,16 @@ module ActiveRecord
913
934
  # about the model. The model needs to pass a specification name to the handler,
914
935
  # in order to look up the correct connection pool.
915
936
  class ConnectionHandler
937
+ def self.create_owner_to_pool # :nodoc:
938
+ Concurrent::Map.new(initial_capacity: 2) do |h, k|
939
+ # Discard the parent's connection pools immediately; we have no need
940
+ # of them
941
+ discard_unowned_pools(h)
942
+
943
+ h[k] = Concurrent::Map.new(initial_capacity: 2)
944
+ end
945
+ end
946
+
916
947
  def self.unowned_pool_finalizer(pid_map) # :nodoc:
917
948
  lambda do |_|
918
949
  discard_unowned_pools(pid_map)
@@ -927,13 +958,7 @@ module ActiveRecord
927
958
 
928
959
  def initialize
929
960
  # These caches are keyed by spec.name (ConnectionSpecification#name).
930
- @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k|
931
- # Discard the parent's connection pools immediately; we have no need
932
- # of them
933
- ConnectionHandler.discard_unowned_pools(h)
934
-
935
- h[k] = Concurrent::Map.new(initial_capacity: 2)
936
- end
961
+ @owner_to_pool = ConnectionHandler.create_owner_to_pool
937
962
 
938
963
  # Backup finalizer: if the forked child never needed a pool, the above
939
964
  # early discard has not occurred
@@ -62,6 +62,11 @@ module ActiveRecord
62
62
  def joins_per_query
63
63
  256
64
64
  end
65
+
66
+ private
67
+ def bind_params_length
68
+ 65535
69
+ end
65
70
  end
66
71
  end
67
72
  end
@@ -20,9 +20,22 @@ module ActiveRecord
20
20
  raise "Passing bind parameters with an arel AST is forbidden. " \
21
21
  "The values must be stored on the AST directly"
22
22
  end
23
- sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
24
- [sql.freeze, binds || []]
23
+
24
+ if prepared_statements
25
+ sql, binds = visitor.accept(arel_or_sql_string.ast, collector).value
26
+
27
+ if binds.length > bind_params_length
28
+ unprepared_statement do
29
+ sql, binds = to_sql_and_binds(arel_or_sql_string)
30
+ visitor.preparable = false
31
+ end
32
+ end
33
+ else
34
+ sql = visitor.accept(arel_or_sql_string.ast, collector).value
35
+ end
36
+ [sql.freeze, binds]
25
37
  else
38
+ visitor.preparable = false if prepared_statements
26
39
  [arel_or_sql_string.dup.freeze, binds]
27
40
  end
28
41
  end
@@ -46,11 +59,11 @@ module ActiveRecord
46
59
  def select_all(arel, name = nil, binds = [], preparable: nil)
47
60
  arel = arel_from_relation(arel)
48
61
  sql, binds = to_sql_and_binds(arel, binds)
49
- if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
50
- preparable = false
51
- else
52
- preparable = visitor.preparable
62
+
63
+ if preparable.nil?
64
+ preparable = prepared_statements ? visitor.preparable : false
53
65
  end
66
+
54
67
  if prepared_statements && preparable
55
68
  select_prepared(sql, name, binds)
56
69
  else
@@ -32,17 +32,17 @@ module ActiveRecord
32
32
  end
33
33
 
34
34
  def enable_query_cache!
35
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
35
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
36
36
  connection.enable_query_cache! if active_connection?
37
37
  end
38
38
 
39
39
  def disable_query_cache!
40
- @query_cache_enabled.delete connection_cache_key(Thread.current)
40
+ @query_cache_enabled.delete connection_cache_key(current_thread)
41
41
  connection.disable_query_cache! if active_connection?
42
42
  end
43
43
 
44
44
  def query_cache_enabled
45
- @query_cache_enabled[connection_cache_key(Thread.current)]
45
+ @query_cache_enabled[connection_cache_key(current_thread)]
46
46
  end
47
47
  end
48
48
 
@@ -96,6 +96,11 @@ module ActiveRecord
96
96
  if @query_cache_enabled && !locked?(arel)
97
97
  arel = arel_from_relation(arel)
98
98
  sql, binds = to_sql_and_binds(arel, binds)
99
+
100
+ if preparable.nil?
101
+ preparable = prepared_statements ? visitor.preparable : false
102
+ end
103
+
99
104
  cache_sql(sql, name, binds) { super(sql, name, binds, preparable: preparable) }
100
105
  else
101
106
  super
@@ -130,6 +130,7 @@ module ActiveRecord
130
130
  end
131
131
 
132
132
  def quoted_time(value) # :nodoc:
133
+ value = value.change(year: 2000, month: 1, day: 1)
133
134
  quoted_date(value).sub(/\A\d\d\d\d-\d\d-\d\d /, "")
134
135
  end
135
136
 
@@ -498,6 +498,9 @@ module ActiveRecord
498
498
  # t.date
499
499
  # t.binary
500
500
  # t.boolean
501
+ # t.foreign_key
502
+ # t.json
503
+ # t.virtual
501
504
  # t.remove
502
505
  # t.remove_references
503
506
  # t.remove_belongs_to
@@ -662,19 +665,19 @@ module ActiveRecord
662
665
 
663
666
  # Adds a foreign key.
664
667
  #
665
- # t.foreign_key(:authors)
668
+ # t.foreign_key(:authors)
666
669
  #
667
670
  # See {connection.add_foreign_key}[rdoc-ref:SchemaStatements#add_foreign_key]
668
- def foreign_key(*args) # :nodoc:
671
+ def foreign_key(*args)
669
672
  @base.add_foreign_key(name, *args)
670
673
  end
671
674
 
672
675
  # Checks to see if a foreign key exists.
673
676
  #
674
- # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
677
+ # t.foreign_key(:authors) unless t.foreign_key_exists?(:authors)
675
678
  #
676
679
  # See {connection.foreign_key_exists?}[rdoc-ref:SchemaStatements#foreign_key_exists?]
677
- def foreign_key_exists?(*args) # :nodoc:
680
+ def foreign_key_exists?(*args)
678
681
  @base.foreign_key_exists?(name, *args)
679
682
  end
680
683
  end
@@ -100,7 +100,7 @@ module ActiveRecord
100
100
  def index_exists?(table_name, column_name, options = {})
101
101
  column_names = Array(column_name).map(&:to_s)
102
102
  checks = []
103
- checks << lambda { |i| i.columns == column_names }
103
+ checks << lambda { |i| Array(i.columns) == column_names }
104
104
  checks << lambda { |i| i.unique } if options[:unique]
105
105
  checks << lambda { |i| i.name == options[:name].to_s } if options[:name]
106
106
 
@@ -305,7 +305,7 @@ module ActiveRecord
305
305
  yield td if block_given?
306
306
 
307
307
  if options[:force]
308
- drop_table(table_name, **options, if_exists: true)
308
+ drop_table(table_name, options.merge(if_exists: true))
309
309
  end
310
310
 
311
311
  result = execute schema_creation.accept td
@@ -741,22 +741,13 @@ module ActiveRecord
741
741
  # ====== Creating an index with a specific operator class
742
742
  #
743
743
  # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops)
744
- #
745
- # generates:
746
- #
747
- # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
744
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
748
745
  #
749
746
  # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
750
- #
751
- # generates:
752
- #
753
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
747
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
754
748
  #
755
749
  # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops)
756
- #
757
- # generates:
758
- #
759
- # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
750
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
760
751
  #
761
752
  # Note: only supported by PostgreSQL
762
753
  #
@@ -908,7 +899,7 @@ module ActiveRecord
908
899
  foreign_key_options = { to_table: reference_name }
909
900
  end
910
901
  foreign_key_options[:column] ||= "#{ref_name}_id"
911
- remove_foreign_key(table_name, **foreign_key_options)
902
+ remove_foreign_key(table_name, foreign_key_options)
912
903
  end
913
904
 
914
905
  remove_column(table_name, "#{ref_name}_id")
@@ -17,11 +17,19 @@ module ActiveRecord
17
17
  end
18
18
 
19
19
  def committed?
20
- @state == :committed
20
+ @state == :committed || @state == :fully_committed
21
+ end
22
+
23
+ def fully_committed?
24
+ @state == :fully_committed
21
25
  end
22
26
 
23
27
  def rolledback?
24
- @state == :rolledback
28
+ @state == :rolledback || @state == :fully_rolledback
29
+ end
30
+
31
+ def fully_rolledback?
32
+ @state == :fully_rolledback
25
33
  end
26
34
 
27
35
  def fully_completed?
@@ -55,10 +63,19 @@ module ActiveRecord
55
63
  @state = :rolledback
56
64
  end
57
65
 
66
+ def full_rollback!
67
+ @children.each { |c| c.rollback! }
68
+ @state = :fully_rolledback
69
+ end
70
+
58
71
  def commit!
59
72
  @state = :committed
60
73
  end
61
74
 
75
+ def full_commit!
76
+ @state = :fully_committed
77
+ end
78
+
62
79
  def nullify!
63
80
  @state = nil
64
81
  end
@@ -89,10 +106,6 @@ module ActiveRecord
89
106
  records << record
90
107
  end
91
108
 
92
- def rollback
93
- @state.rollback!
94
- end
95
-
96
109
  def rollback_records
97
110
  ite = records.uniq
98
111
  while record = ite.shift
@@ -104,10 +117,6 @@ module ActiveRecord
104
117
  end
105
118
  end
106
119
 
107
- def commit
108
- @state.commit!
109
- end
110
-
111
120
  def before_commit_records
112
121
  records.uniq.each(&:before_committed!) if @run_commit_callbacks
113
122
  end
@@ -146,12 +155,12 @@ module ActiveRecord
146
155
 
147
156
  def rollback
148
157
  connection.rollback_to_savepoint(savepoint_name)
149
- super
158
+ @state.rollback!
150
159
  end
151
160
 
152
161
  def commit
153
162
  connection.release_savepoint(savepoint_name)
154
- super
163
+ @state.commit!
155
164
  end
156
165
 
157
166
  def full_rollback?; false; end
@@ -169,12 +178,12 @@ module ActiveRecord
169
178
 
170
179
  def rollback
171
180
  connection.rollback_db_transaction
172
- super
181
+ @state.full_rollback!
173
182
  end
174
183
 
175
184
  def commit
176
185
  connection.commit_db_transaction
177
- super
186
+ @state.full_commit!
178
187
  end
179
188
  end
180
189
 
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
  alias :in_use? :owner
82
82
 
83
83
  def self.type_cast_config_to_integer(config)
84
- if config =~ SIMPLE_INT
84
+ if config.is_a?(Integer)
85
+ config
86
+ elsif config =~ SIMPLE_INT
85
87
  config.to_i
86
88
  else
87
89
  config
@@ -560,17 +560,6 @@ module ActiveRecord
560
560
  @max_allowed_packet ||= (show_variable("max_allowed_packet") - bytes_margin)
561
561
  end
562
562
 
563
- def with_multi_statements
564
- previous_flags = @config[:flags]
565
- @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
566
- reconnect!
567
-
568
- yield
569
- ensure
570
- @config[:flags] = previous_flags
571
- reconnect!
572
- end
573
-
574
563
  def initialize_type_map(m = type_map)
575
564
  super
576
565
 
@@ -815,15 +804,25 @@ module ActiveRecord
815
804
  end
816
805
 
817
806
  def mismatched_foreign_key(message)
818
- parts = message.scan(/`(\w+)`[ $)]/).flatten
819
- MismatchedForeignKey.new(
820
- self,
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 = {
821
814
  message: message,
822
- table: parts[0],
823
- foreign_key: parts[1],
824
- target_table: parts[2],
825
- primary_key: parts[3],
826
- )
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)
827
826
  end
828
827
 
829
828
  def integer_to_sql(limit) # :nodoc:
@@ -195,12 +195,12 @@ module ActiveRecord
195
195
  if e.path == path_to_adapter
196
196
  # We can assume that a non-builtin adapter was specified, so it's
197
197
  # either misspelled or missing from Gemfile.
198
- raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
198
+ raise LoadError, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
199
199
 
200
200
  # Bubbled up from the adapter require. Prefix the exception message
201
201
  # with some guidance about how to address it and reraise.
202
202
  else
203
- raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
203
+ raise LoadError, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace
204
204
  end
205
205
  end
206
206
 
@@ -3,15 +3,24 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module DetermineIfPreparableVisitor
6
- attr_reader :preparable
6
+ attr_accessor :preparable
7
7
 
8
8
  def accept(*)
9
9
  @preparable = true
10
10
  super
11
11
  end
12
12
 
13
- def visit_Arel_Nodes_In(*)
13
+ def visit_Arel_Nodes_In(o, collector)
14
14
  @preparable = false
15
+
16
+ if Array === o.right && !o.right.empty?
17
+ o.right.delete_if do |bind|
18
+ if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value
19
+ !bind.value.boundable?
20
+ end
21
+ end
22
+ end
23
+
15
24
  super
16
25
  end
17
26
 
@@ -62,6 +62,42 @@ module ActiveRecord
62
62
  @connection.next_result while @connection.more_results?
63
63
  end
64
64
 
65
+ def supports_set_server_option?
66
+ @connection.respond_to?(:set_server_option)
67
+ end
68
+
69
+ def multi_statements_enabled?(flags)
70
+ if flags.is_a?(Array)
71
+ flags.include?("MULTI_STATEMENTS")
72
+ else
73
+ (flags & Mysql2::Client::MULTI_STATEMENTS) != 0
74
+ end
75
+ end
76
+
77
+ def with_multi_statements
78
+ previous_flags = @config[:flags]
79
+
80
+ unless multi_statements_enabled?(previous_flags)
81
+ if supports_set_server_option?
82
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
83
+ else
84
+ @config[:flags] = Mysql2::Client::MULTI_STATEMENTS
85
+ reconnect!
86
+ end
87
+ end
88
+
89
+ yield
90
+ ensure
91
+ unless multi_statements_enabled?(previous_flags)
92
+ if supports_set_server_option?
93
+ @connection.set_server_option(Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
94
+ else
95
+ @config[:flags] = previous_flags
96
+ reconnect!
97
+ end
98
+ end
99
+ end
100
+
65
101
  def exec_stmt_and_free(sql, name, binds, cache_stmt: false)
66
102
  # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been
67
103
  # made since we established the connection