activerecord 8.0.0.1 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -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 +2 -2
  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: '08d99704536322248d3c491dc890d68ae27698b4bb368e46a5e9ef40689bf988'
4
- data.tar.gz: 910326d6346f200b00a670487236ad1e67862ba0746c869ea12ea283994ec74a
3
+ metadata.gz: 02db25501500ec8083c47291264f535dc9841c45d56a94e9aff93e826addf5f9
4
+ data.tar.gz: 504ac046514f4f733317b633f311e47ec0ba358d8d94fb92c9341988a49bd203
5
5
  SHA512:
6
- metadata.gz: 32398ee71740d80113c849cf81bf8f8a60531df263042eeb87c38197d28b5f25b5c3805cc70aef9c4228c6b1d3571312ed0b7a1bee7713f02e0c40dec37d39e1
7
- data.tar.gz: 0e6664b8b897e0997bfedc56d5cc3cc2524fb7df4fd4d2f3ee394079cdc08e1648155a75f215d38177e51eddc98a6ca817390a6149ee19eb64a65024f9b30602
6
+ metadata.gz: e13e206ccfcf6c7735ba154e0de054d211a49532206dadd5e0a678afd3c84f14a05da270afec364785f0dac69c117985164a61ca2021a82c0b8b724ea52b6ea0
7
+ data.tar.gz: 38d0436cb8ed82caf1ecad812d36be93c65a48919695a249e2182fa65504f585744362a35aeb93e9e6f83e285e9b34fed398c75e5ff9c97d9a974a952740ab20
data/CHANGELOG.md CHANGED
@@ -1,3 +1,73 @@
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
+
1
71
  ## Rails 8.0.0.1 (December 10, 2024) ##
2
72
 
3
73
  * No changes.
@@ -11,6 +81,7 @@
11
81
 
12
82
  *zzak*
13
83
 
84
+
14
85
  ## Rails 8.0.0.rc2 (October 30, 2024) ##
15
86
 
16
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,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 8
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "1"
12
+ TINY = 1
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -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.1
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-12-10 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.1
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.1
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.1
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.1
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.1/activerecord/CHANGELOG.md
479
- documentation_uri: https://api.rubyonrails.org/v8.0.0.1/
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.1/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: