activerecord 8.1.0.beta1 → 8.1.0.rc1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +105 -4
  3. data/lib/active_record/associations/belongs_to_association.rb +2 -0
  4. data/lib/active_record/attribute_methods/time_zone_conversion.rb +10 -2
  5. data/lib/active_record/attribute_methods.rb +1 -1
  6. data/lib/active_record/autosave_association.rb +2 -2
  7. data/lib/active_record/base.rb +2 -3
  8. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +1 -3
  9. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +31 -29
  10. data/lib/active_record/connection_adapters/abstract/database_statements.rb +32 -13
  11. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  12. data/lib/active_record/connection_adapters/abstract/transaction.rb +9 -0
  13. data/lib/active_record/connection_adapters/abstract_adapter.rb +25 -11
  14. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +0 -2
  15. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +1 -1
  16. data/lib/active_record/connection_adapters/mysql2_adapter.rb +0 -2
  17. data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
  18. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +8 -1
  19. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -5
  20. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +1 -1
  21. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
  22. data/lib/active_record/connection_adapters/postgresql_adapter.rb +16 -5
  23. data/lib/active_record/connection_handling.rb +2 -1
  24. data/lib/active_record/database_configurations/hash_config.rb +5 -2
  25. data/lib/active_record/encryption/encryptor.rb +12 -0
  26. data/lib/active_record/errors.rb +3 -3
  27. data/lib/active_record/explain.rb +1 -1
  28. data/lib/active_record/explain_registry.rb +51 -1
  29. data/lib/active_record/gem_version.rb +1 -1
  30. data/lib/active_record/log_subscriber.rb +1 -1
  31. data/lib/active_record/migration/compatibility.rb +1 -1
  32. data/lib/active_record/model_schema.rb +26 -3
  33. data/lib/active_record/railties/controller_runtime.rb +11 -6
  34. data/lib/active_record/railties/databases.rake +1 -1
  35. data/lib/active_record/railties/job_runtime.rb +2 -2
  36. data/lib/active_record/relation/batches.rb +3 -3
  37. data/lib/active_record/relation/merger.rb +2 -2
  38. data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
  39. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
  40. data/lib/active_record/relation/predicate_builder.rb +7 -5
  41. data/lib/active_record/relation/query_methods.rb +1 -1
  42. data/lib/active_record/relation/where_clause.rb +2 -0
  43. data/lib/active_record/relation.rb +1 -1
  44. data/lib/active_record/runtime_registry.rb +41 -58
  45. data/lib/active_record/structured_event_subscriber.rb +85 -0
  46. data/lib/active_record/table_metadata.rb +5 -20
  47. data/lib/active_record/tasks/database_tasks.rb +24 -14
  48. data/lib/active_record/test_databases.rb +4 -2
  49. data/lib/rails/generators/active_record/application_record/USAGE +1 -1
  50. metadata +9 -9
  51. data/lib/active_record/explain_subscriber.rb +0 -34
@@ -176,6 +176,8 @@ module ActiveRecord
176
176
  end
177
177
 
178
178
  def except_predicates(columns)
179
+ return predicates if columns.empty?
180
+
179
181
  attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
180
182
  non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
181
183
 
@@ -307,7 +307,7 @@ module ActiveRecord
307
307
  end
308
308
  end
309
309
 
310
- # Like #find_or_create_by, but calls {new}[rdoc-ref:Core#new]
310
+ # Like #find_or_create_by, but calls {new}[rdoc-ref:Core.new]
311
311
  # instead of {create}[rdoc-ref:Persistence::ClassMethods#create].
312
312
  def find_or_initialize_by(attributes, &block)
313
313
  find_by(attributes) || new(attributes, &block)
@@ -3,80 +3,63 @@
3
3
  module ActiveRecord
4
4
  # This is a thread locals registry for Active Record. For example:
5
5
  #
6
- # ActiveRecord::RuntimeRegistry.sql_runtime
6
+ # ActiveRecord::RuntimeRegistry.stats.sql_runtime
7
7
  #
8
8
  # returns the connection handler local to the current unit of execution (either thread of fiber).
9
9
  module RuntimeRegistry # :nodoc:
10
- extend self
11
-
12
- def sql_runtime
13
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] ||= 0.0
10
+ class Stats
11
+ attr_accessor :sql_runtime, :async_sql_runtime, :queries_count, :cached_queries_count
12
+
13
+ def initialize
14
+ @sql_runtime = 0.0
15
+ @async_sql_runtime = 0.0
16
+ @queries_count = 0
17
+ @cached_queries_count = 0
18
+ end
19
+
20
+ def reset_runtimes
21
+ sql_runtime_was = @sql_runtime
22
+ @sql_runtime = 0.0
23
+ @async_sql_runtime = 0.0
24
+ sql_runtime_was
25
+ end
26
+
27
+ public alias_method :reset, :initialize
14
28
  end
15
29
 
16
- def sql_runtime=(runtime)
17
- ActiveSupport::IsolatedExecutionState[:active_record_sql_runtime] = runtime
18
- end
19
-
20
- def async_sql_runtime
21
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] ||= 0.0
22
- end
30
+ extend self
23
31
 
24
- def async_sql_runtime=(runtime)
25
- ActiveSupport::IsolatedExecutionState[:active_record_async_sql_runtime] = runtime
32
+ def call(name, start, finish, id, payload)
33
+ record(
34
+ payload[:name],
35
+ (finish - start) * 1_000.0,
36
+ async: payload[:async],
37
+ lock_wait: payload[:lock_wait],
38
+ )
26
39
  end
27
40
 
28
- def queries_count
29
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] ||= 0
30
- end
41
+ def record(query_name, runtime, cached: false, async: false, lock_wait: nil)
42
+ stats = self.stats
31
43
 
32
- def queries_count=(count)
33
- ActiveSupport::IsolatedExecutionState[:active_record_queries_count] = count
34
- end
44
+ unless query_name == "TRANSACTION" || query_name == "SCHEMA"
45
+ stats.queries_count += 1
46
+ stats.cached_queries_count += 1 if cached
47
+ end
35
48
 
36
- def cached_queries_count
37
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] ||= 0
49
+ if async
50
+ stats.async_sql_runtime += (runtime - lock_wait)
51
+ end
52
+ stats.sql_runtime += runtime
38
53
  end
39
54
 
40
- def cached_queries_count=(count)
41
- ActiveSupport::IsolatedExecutionState[:active_record_cached_queries_count] = count
55
+ def stats
56
+ ActiveSupport::IsolatedExecutionState[:active_record_runtime] ||= Stats.new
42
57
  end
43
58
 
44
59
  def reset
45
- reset_runtimes
46
- reset_queries_count
47
- reset_cached_queries_count
48
- end
49
-
50
- def reset_runtimes
51
- rt, self.sql_runtime = sql_runtime, 0.0
52
- self.async_sql_runtime = 0.0
53
- rt
54
- end
55
-
56
- def reset_queries_count
57
- qc = queries_count
58
- self.queries_count = 0
59
- qc
60
- end
61
-
62
- def reset_cached_queries_count
63
- qc = cached_queries_count
64
- self.cached_queries_count = 0
65
- qc
60
+ stats.reset
66
61
  end
67
62
  end
68
63
  end
69
64
 
70
- ActiveSupport::Notifications.monotonic_subscribe("sql.active_record") do |name, start, finish, id, payload|
71
- unless ["SCHEMA", "TRANSACTION"].include?(payload[:name])
72
- ActiveRecord::RuntimeRegistry.queries_count += 1
73
- ActiveRecord::RuntimeRegistry.cached_queries_count += 1 if payload[:cached]
74
- end
75
-
76
- runtime = (finish - start) * 1_000.0
77
-
78
- if payload[:async]
79
- ActiveRecord::RuntimeRegistry.async_sql_runtime += (runtime - payload[:lock_wait])
80
- end
81
- ActiveRecord::RuntimeRegistry.sql_runtime += runtime
82
- end
65
+ ActiveSupport::Notifications.monotonic_subscribe("sql.active_record", ActiveRecord::RuntimeRegistry)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/structured_event_subscriber"
4
+
5
+ module ActiveRecord
6
+ class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
7
+ IGNORE_PAYLOAD_NAMES = ["SCHEMA", "EXPLAIN"]
8
+
9
+ def strict_loading_violation(event)
10
+ owner = event.payload[:owner]
11
+ reflection = event.payload[:reflection]
12
+
13
+ emit_debug_event("active_record.strict_loading_violation",
14
+ owner: owner.name,
15
+ class: reflection.klass.name,
16
+ name: reflection.name,
17
+ )
18
+ end
19
+ debug_only :strict_loading_violation
20
+
21
+ def sql(event)
22
+ payload = event.payload
23
+
24
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
25
+
26
+ binds = nil
27
+
28
+ if payload[:binds]&.any?
29
+ casted_params = type_casted_binds(payload[:type_casted_binds])
30
+
31
+ binds = []
32
+ payload[:binds].each_with_index do |attr, i|
33
+ attribute_name = if attr.respond_to?(:name)
34
+ attr.name
35
+ elsif attr.respond_to?(:[]) && attr[i].respond_to?(:name)
36
+ attr[i].name
37
+ else
38
+ nil
39
+ end
40
+
41
+ filtered_params = filter(attribute_name, casted_params[i])
42
+
43
+ binds << render_bind(attr, filtered_params)
44
+ end
45
+ end
46
+
47
+ emit_debug_event("active_record.sql",
48
+ async: payload[:async],
49
+ name: payload[:name],
50
+ sql: payload[:sql],
51
+ cached: payload[:cached],
52
+ lock_wait: payload[:lock_wait],
53
+ binds: binds,
54
+ duration_ms: event.duration.round(2),
55
+ )
56
+ end
57
+ debug_only :sql
58
+
59
+ private
60
+ def type_casted_binds(casted_binds)
61
+ casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds
62
+ end
63
+
64
+ def render_bind(attr, value)
65
+ case attr
66
+ when ActiveModel::Attribute
67
+ if attr.type.binary? && attr.value
68
+ value = "<#{attr.value_for_database.to_s.bytesize} bytes of binary data>"
69
+ end
70
+ when Array
71
+ attr = attr.first
72
+ else
73
+ attr = nil
74
+ end
75
+
76
+ [attr&.name, value]
77
+ end
78
+
79
+ def filter(name, value)
80
+ ActiveRecord::Base.inspection_filter.filter_param(name, value)
81
+ end
82
+ end
83
+ end
84
+
85
+ ActiveRecord::StructuredEventSubscriber.attach_to :active_record
@@ -2,12 +2,9 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
- delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
6
-
7
- def initialize(klass, arel_table, reflection = nil)
5
+ def initialize(klass, arel_table)
8
6
  @klass = klass
9
7
  @arel_table = arel_table
10
- @reflection = reflection
11
8
  end
12
9
 
13
10
  def primary_key
@@ -22,7 +19,7 @@ module ActiveRecord
22
19
  klass&.columns_hash&.key?(column_name)
23
20
  end
24
21
 
25
- def associated_with?(table_name)
22
+ def associated_with(table_name)
26
23
  klass&._reflect_on_association(table_name)
27
24
  end
28
25
 
@@ -42,26 +39,14 @@ module ActiveRecord
42
39
  if association_klass
43
40
  arel_table = association_klass.arel_table
44
41
  arel_table = arel_table.alias(table_name) if arel_table.name != table_name
45
- TableMetadata.new(association_klass, arel_table, reflection)
42
+ TableMetadata.new(association_klass, arel_table)
46
43
  else
47
44
  type_caster = TypeCaster::Connection.new(klass, table_name)
48
45
  arel_table = Arel::Table.new(table_name, type_caster: type_caster)
49
- TableMetadata.new(nil, arel_table, reflection)
46
+ TableMetadata.new(nil, arel_table)
50
47
  end
51
48
  end
52
49
 
53
- def polymorphic_association?
54
- reflection&.polymorphic?
55
- end
56
-
57
- def polymorphic_name_association
58
- reflection&.polymorphic_name
59
- end
60
-
61
- def through_association?
62
- reflection&.through_reflection?
63
- end
64
-
65
50
  def reflect_on_aggregation(aggregation_name)
66
51
  klass&.reflect_on_aggregation(aggregation_name)
67
52
  end
@@ -78,6 +63,6 @@ module ActiveRecord
78
63
  attr_reader :arel_table
79
64
 
80
65
  private
81
- attr_reader :klass, :reflection
66
+ attr_reader :klass
82
67
  end
83
68
  end
@@ -428,9 +428,15 @@ module ActiveRecord
428
428
  end
429
429
 
430
430
  def dump_all
431
- with_temporary_pool_for_each do |pool|
432
- db_config = pool.db_config
431
+ seen_schemas = []
432
+
433
+ ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
434
+ schema_path = schema_dump_path(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
435
+
436
+ next if seen_schemas.include?(schema_path)
437
+
433
438
  ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config, ENV["SCHEMA_FORMAT"] || db_config.schema_format)
439
+ seen_schemas << schema_path
434
440
  end
435
441
  end
436
442
 
@@ -441,18 +447,22 @@ module ActiveRecord
441
447
  filename = schema_dump_path(db_config, format)
442
448
  return unless filename
443
449
 
444
- FileUtils.mkdir_p(db_dir)
445
- case format.to_sym
446
- when :ruby
447
- File.open(filename, "w:utf-8") do |file|
448
- ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
449
- end
450
- when :sql
451
- structure_dump(db_config, filename)
452
- if migration_connection_pool.schema_migration.table_exists?
453
- File.open(filename, "a") do |f|
454
- f.puts migration_connection.dump_schema_versions
455
- f.print "\n"
450
+ with_temporary_pool(db_config) do |pool|
451
+ FileUtils.mkdir_p(db_dir)
452
+ case format.to_sym
453
+ when :ruby
454
+ File.open(filename, "w:utf-8") do |file|
455
+ ActiveRecord::SchemaDumper.dump(pool, file)
456
+ end
457
+ when :sql
458
+ structure_dump(db_config, filename)
459
+ if pool.schema_migration.table_exists?
460
+ File.open(filename, "a") do |f|
461
+ pool.with_connection do |connection|
462
+ f.puts connection.dump_schema_versions
463
+ end
464
+ f.print "\n"
465
+ end
456
466
  end
457
467
  end
458
468
  end
@@ -19,10 +19,12 @@ module ActiveRecord
19
19
  def self.create_and_load_schema(i, env_name:)
20
20
  old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
21
21
 
22
- ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
22
+ ActiveRecord::Base.configurations.configs_for(env_name: env_name, include_hidden: true).each do |db_config|
23
23
  db_config._database = "#{db_config.database}_#{i}"
24
24
 
25
- ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, nil)
25
+ if db_config.database_tasks?
26
+ ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config, nil)
27
+ end
26
28
  end
27
29
  ensure
28
30
  ActiveRecord::Base.establish_connection
@@ -5,4 +5,4 @@ Example:
5
5
  `bin/rails generate application_record`
6
6
 
7
7
  This generates the base class. A test is not generated because no
8
- behaviour is included in `ApplicationRecord` by default.
8
+ behavior is included in `ApplicationRecord` by default.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.0.beta1
4
+ version: 8.1.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
@@ -15,28 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 8.1.0.beta1
18
+ version: 8.1.0.rc1
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 8.1.0.beta1
25
+ version: 8.1.0.rc1
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: activemodel
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - '='
31
31
  - !ruby/object:Gem::Version
32
- version: 8.1.0.beta1
32
+ version: 8.1.0.rc1
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - '='
38
38
  - !ruby/object:Gem::Version
39
- version: 8.1.0.beta1
39
+ version: 8.1.0.rc1
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: timeout
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -253,7 +253,6 @@ files:
253
253
  - lib/active_record/errors.rb
254
254
  - lib/active_record/explain.rb
255
255
  - lib/active_record/explain_registry.rb
256
- - lib/active_record/explain_subscriber.rb
257
256
  - lib/active_record/filter_attribute_handler.rb
258
257
  - lib/active_record/fixture_set/file.rb
259
258
  - lib/active_record/fixture_set/model_metadata.rb
@@ -337,6 +336,7 @@ files:
337
336
  - lib/active_record/signed_id.rb
338
337
  - lib/active_record/statement_cache.rb
339
338
  - lib/active_record/store.rb
339
+ - lib/active_record/structured_event_subscriber.rb
340
340
  - lib/active_record/suppressor.rb
341
341
  - lib/active_record/table_metadata.rb
342
342
  - lib/active_record/tasks/abstract_tasks.rb
@@ -478,10 +478,10 @@ licenses:
478
478
  - MIT
479
479
  metadata:
480
480
  bug_tracker_uri: https://github.com/rails/rails/issues
481
- changelog_uri: https://github.com/rails/rails/blob/v8.1.0.beta1/activerecord/CHANGELOG.md
482
- documentation_uri: https://api.rubyonrails.org/v8.1.0.beta1/
481
+ changelog_uri: https://github.com/rails/rails/blob/v8.1.0.rc1/activerecord/CHANGELOG.md
482
+ documentation_uri: https://api.rubyonrails.org/v8.1.0.rc1/
483
483
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
484
- source_code_uri: https://github.com/rails/rails/tree/v8.1.0.beta1/activerecord
484
+ source_code_uri: https://github.com/rails/rails/tree/v8.1.0.rc1/activerecord
485
485
  rubygems_mfa_required: 'true'
486
486
  rdoc_options:
487
487
  - "--main"
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/notifications"
4
- require "active_record/explain_registry"
5
-
6
- module ActiveRecord
7
- class ExplainSubscriber # :nodoc:
8
- def start(name, id, payload)
9
- # unused
10
- end
11
-
12
- def finish(name, id, payload)
13
- if ExplainRegistry.collect? && !ignore_payload?(payload)
14
- ExplainRegistry.queries << payload.values_at(:sql, :binds)
15
- end
16
- end
17
-
18
- # SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
19
- # our own EXPLAINs no matter how loopingly beautiful that would be.
20
- #
21
- # On the other hand, we want to monitor the performance of our real database
22
- # queries, not the performance of the access to the query cache.
23
- IGNORED_PAYLOADS = %w(SCHEMA EXPLAIN)
24
- EXPLAINED_SQLS = /\A\s*(\/\*.*\*\/)?\s*(with|select|update|delete|insert)\b/i
25
- def ignore_payload?(payload)
26
- payload[:exception] ||
27
- payload[:cached] ||
28
- IGNORED_PAYLOADS.include?(payload[:name]) ||
29
- !payload[:sql].match?(EXPLAINED_SQLS)
30
- end
31
-
32
- ActiveSupport::Notifications.subscribe("sql.active_record", new)
33
- end
34
- end