activerecord 8.0.0 → 8.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/lib/active_record/associations/belongs_to_association.rb +7 -1
  4. data/lib/active_record/attribute_methods/primary_key.rb +2 -1
  5. data/lib/active_record/attribute_methods.rb +22 -17
  6. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +33 -25
  7. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +4 -0
  8. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -7
  9. data/lib/active_record/connection_adapters/mysql/quoting.rb +7 -1
  10. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -0
  11. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +57 -11
  12. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +2 -0
  13. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +1 -1
  14. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +5 -1
  15. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +2 -0
  16. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
  17. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  18. data/lib/active_record/database_configurations/connection_url_resolver.rb +3 -1
  19. data/lib/active_record/gem_version.rb +1 -1
  20. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -0
  21. data/lib/active_record/relation/query_attribute.rb +1 -1
  22. data/lib/active_record/relation/query_methods.rb +1 -1
  23. data/lib/active_record/schema_dumper.rb +29 -11
  24. data/lib/active_record.rb +4 -2
  25. metadata +9 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fdca074c8ca76d0768b2989fb21aaafa5c31f3b80ce1d22275ccfd9d88dd6910
4
- data.tar.gz: a282424df2605d4c5239f8eedbeb129fe4190e387e7da03292b6ad13e8b2e0ef
3
+ metadata.gz: 02db25501500ec8083c47291264f535dc9841c45d56a94e9aff93e826addf5f9
4
+ data.tar.gz: 504ac046514f4f733317b633f311e47ec0ba358d8d94fb92c9341988a49bd203
5
5
  SHA512:
6
- metadata.gz: f636c65b58ab040b9d820854f97f16df201d08142e50c609bb6acbd1f4817d55c2eeeb3859541c9b26ec47c48b20e01a7ec4e5736a68e6c8ce8d4ecb6342d39a
7
- data.tar.gz: 7768bdbf0ff1b8dc3c369fc3168c0c66a56fcf21c00502043f846b1531a8e5962067987e33e43015172a8f346829362598838df197f5fa59f02ed44e9cb6a583
6
+ metadata.gz: e13e206ccfcf6c7735ba154e0de054d211a49532206dadd5e0a678afd3c84f14a05da270afec364785f0dac69c117985164a61ca2021a82c0b8b724ea52b6ea0
7
+ data.tar.gz: 38d0436cb8ed82caf1ecad812d36be93c65a48919695a249e2182fa65504f585744362a35aeb93e9e6f83e285e9b34fed398c75e5ff9c97d9a974a952740ab20
data/CHANGELOG.md CHANGED
@@ -1,3 +1,78 @@
1
+ ## Rails 8.0.1 (December 13, 2024) ##
2
+
3
+ * Fix removing foreign keys with :restrict action for MySQ
4
+
5
+ *fatkodima*
6
+
7
+ * Fix a race condition in `ActiveRecord::Base#method_missing` when lazily defining attributes.
8
+
9
+ If multiple thread were concurrently triggering attribute definition on the same model,
10
+ it could result in a `NoMethodError` being raised.
11
+
12
+ *Jean Boussier*
13
+
14
+ * Fix MySQL default functions getting dropped when changing a column's nullability.
15
+
16
+ *Bastian Bartmann*
17
+
18
+ * Fix `add_unique_constraint`/`add_check_constraint`/`add_foreign_key` to be revertible when given invalid options.
19
+
20
+ *fatkodima*
21
+
22
+ * Fix asynchronous destroying of polymorphic `belongs_to` associations.
23
+
24
+ *fatkodima*
25
+
26
+ * Fix `insert_all` to not update existing records.
27
+
28
+ *fatkodima*
29
+
30
+ * `NOT VALID` constraints should not dump in `create_table`.
31
+
32
+ *Ryuta Kamizono*
33
+
34
+ * Fix finding by nil composite primary key association.
35
+
36
+ *fatkodima*
37
+
38
+ * Properly reset composite primary key configuration when setting a primary key.
39
+
40
+ *fatkodima*
41
+
42
+ * Fix Mysql2Adapter support for prepared statements
43
+
44
+ Using prepared statements with MySQL could result in a `NoMethodError` exception.
45
+
46
+ *Jean Boussier*, *Leo Arnold*, *zzak*
47
+
48
+ * Fix parsing of SQLite foreign key names when they contain non-ASCII characters
49
+
50
+ *Zacharias Knudsen*
51
+
52
+ * Fix parsing of MySQL 8.0.16+ CHECK constraints when they contain new lines.
53
+
54
+ *Steve Hill*
55
+
56
+ * Ensure normalized attribute queries use `IS NULL` consistently for `nil` and normalized `nil` values.
57
+
58
+ *Joshua Young*
59
+
60
+ * Fix `sum` when performing a grouped calculation.
61
+
62
+ `User.group(:friendly).sum` no longer worked. This is fixed.
63
+
64
+ *Edouard Chin*
65
+
66
+ * Restore back the ability to pass only database name to `DATABASE_URL`.
67
+
68
+ *fatkodima*
69
+
70
+
71
+ ## Rails 8.0.0.1 (December 10, 2024) ##
72
+
73
+ * No changes.
74
+
75
+
1
76
  ## Rails 8.0.0 (November 07, 2024) ##
2
77
 
3
78
  * Fix support for `query_cache: false` in `database.yml`.
@@ -6,6 +81,7 @@
6
81
 
7
82
  *zzak*
8
83
 
84
+
9
85
  ## Rails 8.0.0.rc2 (October 30, 2024) ##
10
86
 
11
87
  * NULLS NOT DISTINCT works with UNIQUE CONSTRAINT as well as UNIQUE INDEX.
@@ -19,10 +19,16 @@ module ActiveRecord
19
19
  id = owner.public_send(reflection.foreign_key)
20
20
  end
21
21
 
22
+ association_class = if reflection.polymorphic?
23
+ owner.public_send(reflection.foreign_type)
24
+ else
25
+ reflection.klass
26
+ end
27
+
22
28
  enqueue_destroy_association(
23
29
  owner_model_name: owner.class.to_s,
24
30
  owner_id: owner.id,
25
- association_class: reflection.klass.to_s,
31
+ association_class: association_class.to_s,
26
32
  association_ids: [id],
27
33
  association_primary_key_column: primary_key_column,
28
34
  ensuring_owner_was_method: options.fetch(:ensuring_owner_was, nil)
@@ -129,12 +129,13 @@ module ActiveRecord
129
129
  # Project.primary_key # => "foo_id"
130
130
  def primary_key=(value)
131
131
  @primary_key = if value.is_a?(Array)
132
- @composite_primary_key = true
133
132
  include CompositePrimaryKey
134
133
  @primary_key = value.map { |v| -v.to_s }.freeze
135
134
  elsif value
136
135
  -value.to_s
137
136
  end
137
+
138
+ @composite_primary_key = value.is_a?(Array)
138
139
  @attributes_builder = nil
139
140
  end
140
141
 
@@ -116,10 +116,11 @@ module ActiveRecord
116
116
  alias_attribute :id_value, :id if _has_attribute?("id")
117
117
  end
118
118
 
119
- @attribute_methods_generated = true
120
-
121
119
  generate_alias_attributes
120
+
121
+ @attribute_methods_generated = true
122
122
  end
123
+
123
124
  true
124
125
  end
125
126
 
@@ -472,23 +473,27 @@ module ActiveRecord
472
473
  end
473
474
 
474
475
  def method_missing(name, ...)
475
- unless self.class.attribute_methods_generated?
476
- if self.class.method_defined?(name)
477
- # The method is explicitly defined in the model, but calls a generated
478
- # method with super. So we must resume the call chain at the right step.
479
- last_method = method(name)
480
- last_method = last_method.super_method while last_method.super_method
481
- self.class.define_attribute_methods
482
- if last_method.super_method
483
- return last_method.super_method.call(...)
484
- end
485
- elsif self.class.define_attribute_methods
486
- # Some attribute methods weren't generated yet, we retry the call
487
- return public_send(name, ...)
488
- end
476
+ # We can't know whether some method was defined or not because
477
+ # multiple thread might be concurrently be in this code path.
478
+ # So the first one would define the methods and the others would
479
+ # appear to already have them.
480
+ self.class.define_attribute_methods
481
+
482
+ # So in all cases we must behave as if the method was just defined.
483
+ method = begin
484
+ self.class.public_instance_method(name)
485
+ rescue NameError
486
+ nil
489
487
  end
490
488
 
491
- super
489
+ # The method might be explicitly defined in the model, but call a generated
490
+ # method with super. So we must resume the call chain at the right step.
491
+ method = method.super_method while method && !method.owner.is_a?(GeneratedAttributeMethods)
492
+ if method
493
+ method.bind_call(self, ...)
494
+ else
495
+ super
496
+ end
492
497
  end
493
498
 
494
499
  def attribute_method?(attr_name)
@@ -117,24 +117,30 @@ module ActiveRecord
117
117
  # * private methods that require being called in a +synchronize+ blocks
118
118
  # are now explicitly documented
119
119
  class ConnectionPool
120
- class WeakThreadKeyMap # :nodoc:
121
- # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
122
- # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
123
- def initialize
124
- @map = {}
125
- end
126
-
127
- def clear
128
- @map.clear
129
- end
120
+ # Prior to 3.3.5, WeakKeyMap had a use after free bug
121
+ # https://bugs.ruby-lang.org/issues/20688
122
+ if ObjectSpace.const_defined?(:WeakKeyMap) && RUBY_VERSION >= "3.3.5"
123
+ WeakThreadKeyMap = ObjectSpace::WeakKeyMap
124
+ else
125
+ class WeakThreadKeyMap # :nodoc:
126
+ # FIXME: On 3.3 we could use ObjectSpace::WeakKeyMap
127
+ # but it currently cause GC crashes: https://github.com/byroot/rails/pull/3
128
+ def initialize
129
+ @map = {}
130
+ end
131
+
132
+ def clear
133
+ @map.clear
134
+ end
130
135
 
131
- def [](key)
132
- @map[key]
133
- end
136
+ def [](key)
137
+ @map[key]
138
+ end
134
139
 
135
- def []=(key, value)
136
- @map.select! { |c, _| c.alive? }
137
- @map[key] = value
140
+ def []=(key, value)
141
+ @map.select! { |c, _| c&.alive? }
142
+ @map[key] = value
143
+ end
138
144
  end
139
145
  end
140
146
 
@@ -687,6 +693,14 @@ module ActiveRecord
687
693
  Thread.pass
688
694
  end
689
695
 
696
+ def new_connection # :nodoc:
697
+ connection = db_config.new_connection
698
+ connection.pool = self
699
+ connection
700
+ rescue ConnectionNotEstablished => ex
701
+ raise ex.set_pool(self)
702
+ end
703
+
690
704
  private
691
705
  def connection_lease
692
706
  @leases[ActiveSupport::IsolatedExecutionState.context]
@@ -866,18 +880,12 @@ module ActiveRecord
866
880
  #--
867
881
  # if owner_thread param is omitted, this must be called in synchronize block
868
882
  def remove_connection_from_thread_cache(conn, owner_thread = conn.owner)
869
- @leases[owner_thread].clear(conn)
883
+ if owner_thread
884
+ @leases[owner_thread].clear(conn)
885
+ end
870
886
  end
871
887
  alias_method :release, :remove_connection_from_thread_cache
872
888
 
873
- def new_connection
874
- connection = db_config.new_connection
875
- connection.pool = self
876
- connection
877
- rescue ConnectionNotEstablished => ex
878
- raise ex.set_pool(self)
879
- end
880
-
881
889
  # If the pool is not at a <tt>@size</tt> limit, establish new connection. Connecting
882
890
  # to the DB is done outside main synchronized section.
883
891
  #--
@@ -159,6 +159,8 @@ module ActiveRecord
159
159
  end
160
160
 
161
161
  def defined_for?(to_table: nil, validate: nil, **options)
162
+ options = options.slice(*self.options.keys)
163
+
162
164
  (to_table.nil? || to_table.to_s == self.to_table) &&
163
165
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
164
166
  options.all? { |k, v| Array(self.options[k]).map(&:to_s) == Array(v).map(&:to_s) }
@@ -185,6 +187,8 @@ module ActiveRecord
185
187
  end
186
188
 
187
189
  def defined_for?(name:, expression: nil, validate: nil, **options)
190
+ options = options.slice(*self.options.keys)
191
+
188
192
  self.name == name.to_s &&
189
193
  (validate.nil? || validate == self.options.fetch(:validate, validate)) &&
190
194
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -403,7 +403,11 @@ module ActiveRecord
403
403
  type ||= column.sql_type
404
404
 
405
405
  unless options.key?(:default)
406
- options[:default] = column.default
406
+ options[:default] = if column.default_function
407
+ -> { column.default_function }
408
+ else
409
+ column.default
410
+ end
407
411
  end
408
412
 
409
413
  unless options.key?(:null)
@@ -628,24 +632,26 @@ module ActiveRecord
628
632
  end
629
633
 
630
634
  def build_insert_sql(insert) # :nodoc:
635
+ # Can use any column as it will be assigned to itself.
631
636
  no_op_column = quote_column_name(insert.keys.first) if insert.keys.first
632
637
 
633
638
  # MySQL 8.0.19 replaces `VALUES(<expression>)` clauses with row and column alias names, see https://dev.mysql.com/worklog/task/?id=6312 .
634
639
  # then MySQL 8.0.20 deprecates the `VALUES(<expression>)` see https://dev.mysql.com/worklog/task/?id=13325 .
635
640
  if supports_insert_raw_alias_syntax?
641
+ quoted_table_name = insert.model.quoted_table_name
636
642
  values_alias = quote_table_name("#{insert.model.table_name.parameterize}_values")
637
643
  sql = +"INSERT #{insert.into} #{insert.values_list} AS #{values_alias}"
638
644
 
639
645
  if insert.skip_duplicates?
640
646
  if no_op_column
641
- sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{values_alias}.#{no_op_column}"
647
+ sql << " ON DUPLICATE KEY UPDATE #{no_op_column}=#{quoted_table_name}.#{no_op_column}"
642
648
  end
643
649
  elsif insert.update_duplicates?
644
650
  if insert.raw_update_sql?
645
651
  sql = +"INSERT #{insert.into} #{insert.values_list} ON DUPLICATE KEY UPDATE #{insert.raw_update_sql}"
646
652
  else
647
653
  sql << " ON DUPLICATE KEY UPDATE "
648
- sql << insert.touch_model_timestamps_unless { |column| "#{insert.model.quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
654
+ sql << insert.touch_model_timestamps_unless { |column| "#{quoted_table_name}.#{column}<=>#{values_alias}.#{column}" }
649
655
  sql << insert.updatable_columns.map { |column| "#{column}=#{values_alias}.#{column}" }.join(",")
650
656
  end
651
657
  end
@@ -746,9 +752,7 @@ module ActiveRecord
746
752
 
747
753
  private
748
754
  def strip_whitespace_characters(expression)
749
- expression = expression.gsub(/\\n|\\\\/, "")
750
- expression = expression.gsub(/\s{2,}/, " ")
751
- expression
755
+ expression.gsub('\\\n', "").gsub("x0A", "").squish
752
756
  end
753
757
 
754
758
  def extended_type_map_key
@@ -762,7 +766,6 @@ module ActiveRecord
762
766
  def handle_warnings(sql)
763
767
  return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
764
768
 
765
- @affected_rows_before_warnings = @raw_connection.affected_rows
766
769
  warning_count = @raw_connection.warning_count
767
770
  result = @raw_connection.query("SHOW WARNINGS")
768
771
  result = [
@@ -102,7 +102,13 @@ module ActiveRecord
102
102
  else
103
103
  value.getlocal
104
104
  end
105
- when Date, Time
105
+ when Time
106
+ if default_timezone == :utc
107
+ value.utc? ? value : value.getutc
108
+ else
109
+ value.utc? ? value.getlocal : value
110
+ end
111
+ when Date
106
112
  value
107
113
  else
108
114
  super
@@ -85,6 +85,13 @@ module ActiveRecord
85
85
  super
86
86
  end
87
87
 
88
+ def remove_foreign_key(from_table, to_table = nil, **options)
89
+ # RESTRICT is by default in MySQL.
90
+ options.delete(:on_update) if options[:on_update] == :restrict
91
+ options.delete(:on_delete) if options[:on_delete] == :restrict
92
+ super
93
+ end
94
+
88
95
  def internal_string_options_for_primary_key
89
96
  super.tap do |options|
90
97
  if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
@@ -48,26 +48,55 @@ module ActiveRecord
48
48
  # made since we established the connection
49
49
  raw_connection.query_options[:database_timezone] = default_timezone
50
50
 
51
- result = if prepare
51
+ result = nil
52
+ if binds.nil? || binds.empty?
53
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
54
+ result = raw_connection.query(sql)
55
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
56
+ # As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
57
+ # from that same connection was GCed while `#query` released the GVL.
58
+ # By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
59
+ # of hitting the bug.
60
+ @affected_rows_before_warnings = result&.size || raw_connection.affected_rows
61
+ end
62
+ elsif prepare
52
63
  stmt = @statements[sql] ||= raw_connection.prepare(sql)
53
-
54
64
  begin
55
65
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
56
- stmt.execute(*type_casted_binds)
66
+ result = stmt.execute(*type_casted_binds)
67
+ @affected_rows_before_warnings = stmt.affected_rows
57
68
  end
58
69
  rescue ::Mysql2::Error
59
70
  @statements.delete(sql)
60
- stmt.close
61
71
  raise
62
72
  end
63
- verified!
64
73
  else
65
- raw_connection.query(sql)
74
+ stmt = raw_connection.prepare(sql)
75
+
76
+ begin
77
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
78
+ result = stmt.execute(*type_casted_binds)
79
+ @affected_rows_before_warnings = stmt.affected_rows
80
+ end
81
+
82
+ # Ref: https://github.com/brianmario/mysql2/pull/1383
83
+ # by eagerly closing uncached prepared statements, we also reduce the chances of
84
+ # that bug happening. It can still happen if `#execute` is used as we have no callback
85
+ # to eagerly close the statement.
86
+ if result
87
+ result.instance_variable_set(:@_ar_stmt_to_close, stmt)
88
+ else
89
+ stmt.close
90
+ end
91
+ rescue ::Mysql2::Error
92
+ stmt.close
93
+ raise
94
+ end
66
95
  end
67
96
 
97
+ notification_payload[:affected_rows] = @affected_rows_before_warnings
68
98
  notification_payload[:row_count] = result&.size || 0
69
99
 
70
- @affected_rows_before_warnings = raw_connection.affected_rows
71
100
  raw_connection.abandon_results!
72
101
 
73
102
  verified!
@@ -79,17 +108,34 @@ module ActiveRecord
79
108
  end
80
109
  end
81
110
 
82
- def cast_result(result)
83
- if result.nil? || result.fields.empty?
111
+ def cast_result(raw_result)
112
+ return ActiveRecord::Result.empty if raw_result.nil?
113
+
114
+ fields = raw_result.fields
115
+
116
+ result = if fields.empty?
84
117
  ActiveRecord::Result.empty
85
118
  else
86
- ActiveRecord::Result.new(result.fields, result.to_a)
119
+ ActiveRecord::Result.new(fields, raw_result.to_a)
87
120
  end
121
+
122
+ free_raw_result(raw_result)
123
+
124
+ result
88
125
  end
89
126
 
90
- def affected_rows(result)
127
+ def affected_rows(raw_result)
128
+ free_raw_result(raw_result) if raw_result
129
+
91
130
  @affected_rows_before_warnings
92
131
  end
132
+
133
+ def free_raw_result(raw_result)
134
+ raw_result.free
135
+ if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
136
+ stmt.close
137
+ end
138
+ end
93
139
  end
94
140
  end
95
141
  end
@@ -233,6 +233,8 @@ module ActiveRecord
233
233
  end
234
234
 
235
235
  def defined_for?(name: nil, column: nil, **options)
236
+ options = options.slice(*self.options.keys)
237
+
236
238
  (name.nil? || self.name == name.to_s) &&
237
239
  (column.nil? || Array(self.column) == Array(column).map(&:to_s)) &&
238
240
  options.all? { |k, v| self.options[k].to_s == v.to_s }
@@ -922,7 +922,7 @@ module ActiveRecord
922
922
  #
923
923
  # validate_check_constraint :products, name: "price_check"
924
924
  #
925
- # The +options+ hash accepts the same keys as add_check_constraint[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
925
+ # The +options+ hash accepts the same keys as {add_check_constraint}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_check_constraint].
926
926
  def validate_check_constraint(table_name, **options)
927
927
  chk_name_to_validate = check_constraint_for!(table_name, **options).name
928
928
 
@@ -137,7 +137,11 @@ module ActiveRecord
137
137
  end
138
138
 
139
139
  def default_insert_value(column)
140
- column.default
140
+ if column.default_function
141
+ Arel.sql(column.default_function)
142
+ else
143
+ column.default
144
+ end
141
145
  end
142
146
  end
143
147
  end
@@ -27,6 +27,7 @@ module ActiveRecord
27
27
  col["name"]
28
28
  end
29
29
 
30
+ where = where.sub(/\s*\/\*.*\*\/\z/, "") if where
30
31
  orders = {}
31
32
 
32
33
  if columns.any?(&:nil?) # index created with an expression
@@ -74,6 +75,7 @@ module ActiveRecord
74
75
  Base.pluralize_table_names ? table.pluralize : table
75
76
  end
76
77
  table = strip_table_name_prefix_and_suffix(table)
78
+ options = options.slice(*fk.options.keys)
77
79
  fk_to_table = strip_table_name_prefix_and_suffix(fk.to_table)
78
80
  fk_to_table == table && options.all? { |k, v| fk.options[k].to_s == v.to_s }
79
81
  end || raise(ArgumentError, "Table '#{from_table}' has no foreign key for #{to_table || options}")
@@ -407,7 +407,7 @@ module ActiveRecord
407
407
  end
408
408
  alias :add_belongs_to :add_reference
409
409
 
410
- FK_REGEX = /.*FOREIGN KEY\s+\("(\w+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
410
+ FK_REGEX = /.*FOREIGN KEY\s+\("([^"]+)"\)\s+REFERENCES\s+"(\w+)"\s+\("(\w+)"\)/
411
411
  DEFERRABLE_REGEX = /DEFERRABLE INITIALLY (\w+)/
412
412
  def foreign_keys(table_name)
413
413
  # SQLite returns 1 row for each column of composite foreign keys.
@@ -50,8 +50,10 @@ module ActiveRecord
50
50
  end
51
51
 
52
52
  def delete(key)
53
- dealloc cache[key]
54
- cache.delete(key)
53
+ if stmt = cache.delete(key)
54
+ dealloc(stmt)
55
+ end
56
+ stmt
55
57
  end
56
58
 
57
59
  private
@@ -81,7 +81,9 @@ module ActiveRecord
81
81
 
82
82
  def resolved_adapter
83
83
  adapter = uri.scheme && @uri.scheme.tr("-", "_")
84
- adapter = ActiveRecord.protocol_adapters[adapter] || adapter
84
+ if adapter && ActiveRecord.protocol_adapters[adapter]
85
+ adapter = ActiveRecord.protocol_adapters[adapter]
86
+ end
85
87
  adapter
86
88
  end
87
89
 
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 0
12
- TINY = 0
12
+ TINY = 1
13
13
  PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
@@ -59,6 +59,8 @@ module ActiveRecord
59
59
  def convert_to_id(value)
60
60
  if primary_key.is_a?(Array)
61
61
  primary_key.map do |attribute|
62
+ next nil if value.nil?
63
+
62
64
  if attribute == "id"
63
65
  value.id_value
64
66
  else
@@ -35,7 +35,7 @@ module ActiveRecord
35
35
  def nil?
36
36
  unless value_before_type_cast.is_a?(StatementCache::Substitute)
37
37
  value_before_type_cast.nil? ||
38
- type.respond_to?(:subtype) && serializable? && value_for_database.nil?
38
+ (type.respond_to?(:subtype) || type.respond_to?(:normalizer)) && serializable? && value_for_database.nil?
39
39
  end
40
40
  end
41
41
 
@@ -1990,7 +1990,7 @@ module ActiveRecord
1990
1990
  def arel_column(field)
1991
1991
  field = field.name if is_symbol = field.is_a?(Symbol)
1992
1992
 
1993
- field = model.attribute_aliases[field] || field
1993
+ field = model.attribute_aliases[field] || field.to_s
1994
1994
  from = from_clause.name || from_clause.value
1995
1995
 
1996
1996
  if model.columns_hash.key?(field) && (!from || table_name_matches?(from))
@@ -207,12 +207,17 @@ module ActiveRecord
207
207
  end
208
208
 
209
209
  indexes_in_create(table, tbl)
210
- check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
210
+ remaining = check_constraints_in_create(table, tbl) if @connection.supports_check_constraints?
211
211
  exclusion_constraints_in_create(table, tbl) if @connection.supports_exclusion_constraints?
212
212
  unique_constraints_in_create(table, tbl) if @connection.supports_unique_constraints?
213
213
 
214
214
  tbl.puts " end"
215
215
 
216
+ if remaining
217
+ tbl.puts
218
+ tbl.print remaining.string
219
+ end
220
+
216
221
  stream.print tbl.string
217
222
  rescue => e
218
223
  stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
@@ -277,24 +282,37 @@ module ActiveRecord
277
282
 
278
283
  def check_constraints_in_create(table, stream)
279
284
  if (check_constraints = @connection.check_constraints(table)).any?
280
- add_check_constraint_statements = check_constraints.map do |check_constraint|
281
- parts = [
282
- "t.check_constraint #{check_constraint.expression.inspect}"
283
- ]
285
+ check_valid, check_invalid = check_constraints.partition { |chk| chk.validate? }
284
286
 
285
- if check_constraint.export_name_on_schema_dump?
286
- parts << "name: #{check_constraint.name.inspect}"
287
+ unless check_valid.empty?
288
+ check_constraint_statements = check_valid.map do |check|
289
+ " t.check_constraint #{check_parts(check).join(', ')}"
287
290
  end
288
291
 
289
- parts << "validate: #{check_constraint.validate?.inspect}" unless check_constraint.validate?
290
-
291
- " #{parts.join(', ')}"
292
+ stream.puts check_constraint_statements.sort.join("\n")
292
293
  end
293
294
 
294
- stream.puts add_check_constraint_statements.sort.join("\n")
295
+ unless check_invalid.empty?
296
+ remaining = StringIO.new
297
+ table_name = remove_prefix_and_suffix(table).inspect
298
+
299
+ add_check_constraint_statements = check_invalid.map do |check|
300
+ " add_check_constraint #{([table_name] + check_parts(check)).join(', ')}"
301
+ end
302
+
303
+ remaining.puts add_check_constraint_statements.sort.join("\n")
304
+ remaining
305
+ end
295
306
  end
296
307
  end
297
308
 
309
+ def check_parts(check)
310
+ check_parts = [ check.expression.inspect ]
311
+ check_parts << "name: #{check.name.inspect}" if check.export_name_on_schema_dump?
312
+ check_parts << "validate: #{check.validate?.inspect}" unless check.validate?
313
+ check_parts
314
+ end
315
+
298
316
  def foreign_keys(table, stream)
299
317
  if (foreign_keys = @connection.foreign_keys(table)).any?
300
318
  add_foreign_key_statements = foreign_keys.map do |foreign_key|
data/lib/active_record.rb CHANGED
@@ -548,8 +548,10 @@ module ActiveRecord
548
548
  open_transactions = []
549
549
  Base.connection_handler.each_connection_pool do |pool|
550
550
  if active_connection = pool.active_connection
551
- if active_connection.current_transaction.open? && active_connection.current_transaction.joinable?
552
- open_transactions << active_connection.current_transaction
551
+ current_transaction = active_connection.current_transaction
552
+
553
+ if current_transaction.open? && current_transaction.joinable? && !current_transaction.state.invalidated?
554
+ open_transactions << current_transaction
553
555
  end
554
556
  end
555
557
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0
4
+ version: 8.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-07 00:00:00.000000000 Z
11
+ date: 2024-12-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 8.0.0
19
+ version: 8.0.1
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 8.0.0
26
+ version: 8.0.1
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activemodel
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 8.0.0
33
+ version: 8.0.1
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 8.0.0
40
+ version: 8.0.1
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: timeout
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -475,10 +475,10 @@ licenses:
475
475
  - MIT
476
476
  metadata:
477
477
  bug_tracker_uri: https://github.com/rails/rails/issues
478
- changelog_uri: https://github.com/rails/rails/blob/v8.0.0/activerecord/CHANGELOG.md
479
- documentation_uri: https://api.rubyonrails.org/v8.0.0/
478
+ changelog_uri: https://github.com/rails/rails/blob/v8.0.1/activerecord/CHANGELOG.md
479
+ documentation_uri: https://api.rubyonrails.org/v8.0.1/
480
480
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
481
- source_code_uri: https://github.com/rails/rails/tree/v8.0.0/activerecord
481
+ source_code_uri: https://github.com/rails/rails/tree/v8.0.1/activerecord
482
482
  rubygems_mfa_required: 'true'
483
483
  post_install_message:
484
484
  rdoc_options: