activerecord 6.0.0.rc2 → 6.0.1.rc1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +101 -3
  3. data/lib/active_record/association_relation.rb +15 -6
  4. data/lib/active_record/associations.rb +1 -1
  5. data/lib/active_record/associations/association.rb +9 -1
  6. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -1
  7. data/lib/active_record/associations/join_dependency.rb +4 -0
  8. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  9. data/lib/active_record/associations/preloader.rb +1 -1
  10. data/lib/active_record/autosave_association.rb +7 -3
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +25 -10
  12. data/lib/active_record/connection_adapters/abstract/database_statements.rb +4 -0
  13. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -1
  14. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -3
  16. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +13 -4
  17. data/lib/active_record/connection_adapters/connection_specification.rb +2 -2
  18. data/lib/active_record/connection_adapters/mysql/database_statements.rb +3 -1
  19. data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -2
  20. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +3 -1
  21. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  22. data/lib/active_record/connection_adapters/postgresql_adapter.rb +4 -0
  23. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +3 -1
  24. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +4 -0
  25. data/lib/active_record/connection_handling.rb +11 -4
  26. data/lib/active_record/core.rb +8 -4
  27. data/lib/active_record/database_configurations.rb +60 -31
  28. data/lib/active_record/enum.rb +9 -0
  29. data/lib/active_record/fixtures.rb +11 -6
  30. data/lib/active_record/gem_version.rb +2 -2
  31. data/lib/active_record/insert_all.rb +2 -3
  32. data/lib/active_record/middleware/database_selector/resolver.rb +4 -6
  33. data/lib/active_record/migration.rb +13 -5
  34. data/lib/active_record/model_schema.rb +3 -0
  35. data/lib/active_record/railties/databases.rake +6 -3
  36. data/lib/active_record/relation.rb +1 -0
  37. data/lib/active_record/relation/calculations.rb +1 -3
  38. data/lib/active_record/relation/finder_methods.rb +10 -1
  39. data/lib/active_record/relation/query_methods.rb +47 -21
  40. data/lib/active_record/tasks/database_tasks.rb +35 -0
  41. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -1
  42. data/lib/active_record/test_databases.rb +1 -16
  43. data/lib/active_record/transactions.rb +1 -1
  44. data/lib/active_record/validations.rb +1 -0
  45. data/lib/arel.rb +12 -5
  46. metadata +12 -9
@@ -110,6 +110,14 @@ module ActiveRecord
110
110
  !mariadb? && database_version >= "5.7.7"
111
111
  end
112
112
 
113
+ def supports_common_table_expressions?
114
+ if mariadb?
115
+ database_version >= "10.2.1"
116
+ else
117
+ database_version >= "8.0.1"
118
+ end
119
+ end
120
+
113
121
  def supports_advisory_locks?
114
122
  true
115
123
  end
@@ -440,11 +448,11 @@ module ActiveRecord
440
448
 
441
449
  query_values(<<~SQL, "SCHEMA")
442
450
  SELECT column_name
443
- FROM information_schema.key_column_usage
444
- WHERE constraint_name = 'PRIMARY'
451
+ FROM information_schema.statistics
452
+ WHERE index_name = 'PRIMARY'
445
453
  AND table_schema = #{scope[:schema]}
446
454
  AND table_name = #{scope[:name]}
447
- ORDER BY ordinal_position
455
+ ORDER BY seq_in_index
448
456
  SQL
449
457
  end
450
458
 
@@ -581,6 +589,7 @@ module ActiveRecord
581
589
  end
582
590
 
583
591
  # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html
592
+ ER_FILSORT_ABORT = 1028
584
593
  ER_DUP_ENTRY = 1062
585
594
  ER_NOT_NULL_VIOLATION = 1048
586
595
  ER_NO_REFERENCED_ROW = 1216
@@ -622,7 +631,7 @@ module ActiveRecord
622
631
  Deadlocked.new(message, sql: sql, binds: binds)
623
632
  when ER_LOCK_WAIT_TIMEOUT
624
633
  LockWaitTimeout.new(message, sql: sql, binds: binds)
625
- when ER_QUERY_TIMEOUT
634
+ when ER_QUERY_TIMEOUT, ER_FILSORT_ABORT
626
635
  StatementTimeout.new(message, sql: sql, binds: binds)
627
636
  when ER_QUERY_INTERRUPTED
628
637
  QueryCanceled.new(message, sql: sql, binds: binds)
@@ -186,7 +186,7 @@ module ActiveRecord
186
186
  adapter_method = "#{spec[:adapter]}_connection"
187
187
 
188
188
  unless ActiveRecord::Base.respond_to?(adapter_method)
189
- raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
189
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec[:adapter]} adapter"
190
190
  end
191
191
 
192
192
  ConnectionSpecification.new(spec.delete(:name) || "primary", spec, adapter_method)
@@ -222,7 +222,7 @@ module ActiveRecord
222
222
  when Hash
223
223
  resolve_hash_connection config_or_env
224
224
  else
225
- resolve_connection config_or_env
225
+ raise TypeError, "Invalid type for configuration. Expected Symbol, String, or Hash. Got #{config_or_env.inspect}"
226
226
  end
227
227
  end
228
228
 
@@ -19,7 +19,9 @@ module ActiveRecord
19
19
  execute(sql, name).to_a
20
20
  end
21
21
 
22
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
22
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
23
+ :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :describe, :desc, :with
24
+ ) # :nodoc:
23
25
  private_constant :READ_QUERY
24
26
 
25
27
  def write_query?(sql) # :nodoc:
@@ -39,8 +39,8 @@ module ActiveRecord
39
39
  include MySQL::DatabaseStatements
40
40
 
41
41
  def initialize(connection, logger, connection_options, config)
42
- super
43
- @prepared_statements = false unless config.key?(:prepared_statements)
42
+ superclass_config = config.reverse_merge(prepared_statements: false)
43
+ super(connection, logger, connection_options, superclass_config)
44
44
  configure_connection
45
45
  end
46
46
 
@@ -67,7 +67,9 @@ module ActiveRecord
67
67
  end
68
68
  end
69
69
 
70
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback) # :nodoc:
70
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
71
+ :begin, :commit, :explain, :select, :set, :show, :release, :savepoint, :rollback, :with
72
+ ) # :nodoc:
71
73
  private_constant :READ_QUERY
72
74
 
73
75
  def write_query?(sql) # :nodoc:
@@ -26,9 +26,9 @@ module ActiveRecord
26
26
 
27
27
  value = value.sub(/^\((.+)\)$/, '-\1') # (4)
28
28
  case value
29
- when /^-?\D+[\d,]+\.\d{2}$/ # (1)
29
+ when /^-?\D*[\d,]+\.\d{2}$/ # (1)
30
30
  value.gsub!(/[^-\d.]/, "")
31
- when /^-?\D+[\d.]+,\d{2}$/ # (2)
31
+ when /^-?\D*[\d.]+,\d{2}$/ # (2)
32
32
  value.gsub!(/[^-\d,]/, "").sub!(/,/, ".")
33
33
  end
34
34
 
@@ -361,6 +361,10 @@ module ActiveRecord
361
361
  @has_pg_hint_plan
362
362
  end
363
363
 
364
+ def supports_common_table_expressions?
365
+ true
366
+ end
367
+
364
368
  def supports_lazy_transactions?
365
369
  true
366
370
  end
@@ -4,7 +4,9 @@ module ActiveRecord
4
4
  module ConnectionAdapters
5
5
  module SQLite3
6
6
  module DatabaseStatements
7
- READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(:begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback) # :nodoc:
7
+ READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
8
+ :begin, :commit, :explain, :select, :pragma, :release, :savepoint, :rollback, :with
9
+ ) # :nodoc:
8
10
  private_constant :READ_QUERY
9
11
 
10
12
  def write_query?(sql) # :nodoc:
@@ -144,6 +144,10 @@ module ActiveRecord
144
144
  true
145
145
  end
146
146
 
147
+ def supports_common_table_expressions?
148
+ database_version >= "3.8.3"
149
+ end
150
+
147
151
  def supports_insert_on_conflict?
148
152
  database_version >= "3.24.0"
149
153
  end
@@ -113,8 +113,9 @@ module ActiveRecord
113
113
  # Dog.run_a_long_query
114
114
  # end
115
115
  #
116
- # When using the database key a new connection will be established every time.
117
- def connected_to(database: nil, role: nil, &blk)
116
+ # When using the database key a new connection will be established every time. It is not
117
+ # recommended to use this outside of one-off scripts.
118
+ def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
118
119
  if database && role
119
120
  raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
120
121
  elsif database
@@ -130,7 +131,13 @@ module ActiveRecord
130
131
 
131
132
  with_handler(role, &blk)
132
133
  elsif role
133
- with_handler(role.to_sym, &blk)
134
+ if role == writing_role
135
+ with_handler(role.to_sym) do
136
+ connection_handler.while_preventing_writes(prevent_writes, &blk)
137
+ end
138
+ else
139
+ with_handler(role.to_sym, &blk)
140
+ end
134
141
  else
135
142
  raise ArgumentError, "must provide a `database` or a `role`."
136
143
  end
@@ -204,7 +211,7 @@ module ActiveRecord
204
211
  # Return the specification name from the current class or its parent.
205
212
  def connection_specification_name
206
213
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
207
- return primary_class? ? "primary" : superclass.connection_specification_name
214
+ return self == Base ? "primary" : superclass.connection_specification_name
208
215
  end
209
216
  @connection_specification_name
210
217
  end
@@ -586,12 +586,16 @@ module ActiveRecord
586
586
  self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
587
587
  end
588
588
 
589
+ class InspectionMask < DelegateClass(::String)
590
+ def pretty_print(pp)
591
+ pp.text __getobj__
592
+ end
593
+ end
594
+ private_constant :InspectionMask
595
+
589
596
  def inspection_filter
590
597
  @inspection_filter ||= begin
591
- mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
592
- def mask.pretty_print(pp)
593
- pp.text __getobj__
594
- end
598
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
595
599
  ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
596
600
  end
597
601
  end
@@ -9,6 +9,8 @@ module ActiveRecord
9
9
  # objects (either a HashConfig or UrlConfig) that are constructed from the
10
10
  # application's database configuration hash or URL string.
11
11
  class DatabaseConfigurations
12
+ class InvalidConfigurationError < StandardError; end
13
+
12
14
  attr_reader :configurations
13
15
  delegate :any?, to: :configurations
14
16
 
@@ -91,6 +93,19 @@ module ActiveRecord
91
93
  end
92
94
  alias :blank? :empty?
93
95
 
96
+ def each
97
+ throw_getter_deprecation(:each)
98
+ configurations.each { |config|
99
+ yield [config.env_name, config.config]
100
+ }
101
+ end
102
+
103
+ def first
104
+ throw_getter_deprecation(:first)
105
+ config = configurations.first
106
+ [config.env_name, config.config]
107
+ end
108
+
94
109
  private
95
110
  def env_with_configs(env = nil)
96
111
  if env
@@ -104,34 +119,48 @@ module ActiveRecord
104
119
  return configs.configurations if configs.is_a?(DatabaseConfigurations)
105
120
  return configs if configs.is_a?(Array)
106
121
 
107
- build_db_config = configs.each_pair.flat_map do |env_name, config|
108
- walk_configs(env_name.to_s, "primary", config)
109
- end.flatten.compact
122
+ db_configs = configs.flat_map do |env_name, config|
123
+ if config.is_a?(Hash) && config.all? { |_, v| v.is_a?(Hash) }
124
+ walk_configs(env_name.to_s, config)
125
+ else
126
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
127
+ end
128
+ end
110
129
 
111
- if url = ENV["DATABASE_URL"]
112
- build_url_config(url, build_db_config)
113
- else
114
- build_db_config
130
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
131
+
132
+ unless db_configs.find(&:for_current_env?)
133
+ db_configs << environment_url_config(current_env, "primary", {})
134
+ end
135
+
136
+ merge_db_environment_variables(current_env, db_configs.compact)
137
+ end
138
+
139
+ def walk_configs(env_name, config)
140
+ config.map do |spec_name, sub_config|
141
+ build_db_config_from_raw_config(env_name, spec_name.to_s, sub_config)
115
142
  end
116
143
  end
117
144
 
118
- def walk_configs(env_name, spec_name, config)
145
+ def build_db_config_from_raw_config(env_name, spec_name, config)
119
146
  case config
120
147
  when String
121
148
  build_db_config_from_string(env_name, spec_name, config)
122
149
  when Hash
123
150
  build_db_config_from_hash(env_name, spec_name, config.stringify_keys)
151
+ else
152
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
124
153
  end
125
154
  end
126
155
 
127
156
  def build_db_config_from_string(env_name, spec_name, config)
128
157
  url = config
129
158
  uri = URI.parse(url)
130
- if uri.try(:scheme)
159
+ if uri.scheme
131
160
  ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url)
161
+ else
162
+ raise InvalidConfigurationError, "'{ #{env_name} => #{config} }' is not a valid configuration. Expected '#{config}' to be a URL string or a Hash."
132
163
  end
133
- rescue URI::InvalidURIError
134
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
135
164
  end
136
165
 
137
166
  def build_db_config_from_hash(env_name, spec_name, config)
@@ -141,36 +170,36 @@ module ActiveRecord
141
170
  config_without_url.delete "url"
142
171
 
143
172
  ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
144
- elsif config["database"] || config["adapter"] || ENV["DATABASE_URL"]
145
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
146
173
  else
147
- config.each_pair.map do |sub_spec_name, sub_config|
148
- walk_configs(env_name, sub_spec_name, sub_config)
149
- end
174
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
150
175
  end
151
176
  end
152
177
 
153
- def build_url_config(url, configs)
154
- env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
178
+ def merge_db_environment_variables(current_env, configs)
179
+ configs.map do |config|
180
+ next config if config.url_config? || config.env_name != current_env
155
181
 
156
- if configs.find(&:for_current_env?)
157
- configs.map do |config|
158
- if config.url_config?
159
- config
160
- else
161
- ActiveRecord::DatabaseConfigurations::UrlConfig.new(config.env_name, config.spec_name, url, config.config)
162
- end
163
- end
164
- else
165
- configs + [ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, "primary", url)]
182
+ url_config = environment_url_config(current_env, config.spec_name, config.config)
183
+ url_config || config
166
184
  end
167
185
  end
168
186
 
187
+ def environment_url_config(env, spec_name, config)
188
+ url = environment_value_for(spec_name)
189
+ return unless url
190
+
191
+ ActiveRecord::DatabaseConfigurations::UrlConfig.new(env, spec_name, url, config)
192
+ end
193
+
194
+ def environment_value_for(spec_name)
195
+ spec_env_key = "#{spec_name.upcase}_DATABASE_URL"
196
+ url = ENV[spec_env_key]
197
+ url ||= ENV["DATABASE_URL"] if spec_name == "primary"
198
+ url
199
+ end
200
+
169
201
  def method_missing(method, *args, &blk)
170
202
  case method
171
- when :each, :first
172
- throw_getter_deprecation(method)
173
- configurations.send(method, *args, &blk)
174
203
  when :fetch
175
204
  throw_getter_deprecation(method)
176
205
  configs_for(env_name: args.first)
@@ -200,6 +200,8 @@ module ActiveRecord
200
200
  # scope :active, -> { where(status: 0) }
201
201
  # scope :not_active, -> { where.not(status: 0) }
202
202
  if enum_scopes != false
203
+ klass.send(:detect_negative_condition!, value_method_name)
204
+
203
205
  klass.send(:detect_enum_conflict!, name, value_method_name, true)
204
206
  klass.scope value_method_name, -> { where(attr => value) }
205
207
 
@@ -261,5 +263,12 @@ module ActiveRecord
261
263
  source: source
262
264
  }
263
265
  end
266
+
267
+ def detect_negative_condition!(method_name)
268
+ if method_name.start_with?("not_") && logger
269
+ logger.warn "An enum element in #{self.name} uses the prefix 'not_'." \
270
+ " This will cause a conflict with auto generated negative scopes."
271
+ end
272
+ end
264
273
  end
265
274
  end
@@ -531,15 +531,15 @@ module ActiveRecord
531
531
  end
532
532
  end
533
533
 
534
- def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
534
+ def create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base, &block)
535
535
  fixture_set_names = Array(fixture_set_names).map(&:to_s)
536
536
  class_names = ClassCache.new class_names, config
537
537
 
538
538
  # FIXME: Apparently JK uses this.
539
- connection = block_given? ? yield : ActiveRecord::Base.connection
539
+ connection = block_given? ? block : lambda { ActiveRecord::Base.connection }
540
540
 
541
541
  fixture_files_to_read = fixture_set_names.reject do |fs_name|
542
- fixture_is_cached?(connection, fs_name)
542
+ fixture_is_cached?(connection.call, fs_name)
543
543
  end
544
544
 
545
545
  if fixture_files_to_read.any?
@@ -549,9 +549,9 @@ module ActiveRecord
549
549
  class_names,
550
550
  connection,
551
551
  )
552
- cache_fixtures(connection, fixtures_map)
552
+ cache_fixtures(connection.call, fixtures_map)
553
553
  end
554
- cached_fixtures(connection, fixture_set_names)
554
+ cached_fixtures(connection.call, fixture_set_names)
555
555
  end
556
556
 
557
557
  # Returns a consistent, platform-independent identifier for +label+.
@@ -591,7 +591,11 @@ module ActiveRecord
591
591
 
592
592
  def insert(fixture_sets, connection) # :nodoc:
593
593
  fixture_sets_by_connection = fixture_sets.group_by do |fixture_set|
594
- fixture_set.model_class&.connection || connection
594
+ if fixture_set.model_class
595
+ fixture_set.model_class.connection
596
+ else
597
+ connection.call
598
+ end
595
599
  end
596
600
 
597
601
  fixture_sets_by_connection.each do |conn, set|
@@ -602,6 +606,7 @@ module ActiveRecord
602
606
  table_rows_for_connection[table].unshift(*rows)
603
607
  end
604
608
  end
609
+
605
610
  conn.insert_fixtures_set(table_rows_for_connection, table_rows_for_connection.keys)
606
611
 
607
612
  # Cap primary key sequences to max(pk).
@@ -9,8 +9,8 @@ module ActiveRecord
9
9
  module VERSION
10
10
  MAJOR = 6
11
11
  MINOR = 0
12
- TINY = 0
13
- PRE = "rc2"
12
+ TINY = 1
13
+ PRE = "rc1"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -24,7 +24,7 @@ module ActiveRecord
24
24
  message = +"#{model} "
25
25
  message << "Bulk " if inserts.many?
26
26
  message << (on_duplicate == :update ? "Upsert" : "Insert")
27
- connection.exec_query to_sql, message
27
+ connection.exec_insert_all to_sql, message
28
28
  end
29
29
 
30
30
  def updatable_columns
@@ -110,8 +110,7 @@ module ActiveRecord
110
110
  end
111
111
  end
112
112
 
113
-
114
- class Builder
113
+ class Builder # :nodoc:
115
114
  attr_reader :model
116
115
 
117
116
  delegate :skip_duplicates?, :update_duplicates?, :keys, to: :insert_all