activerecord 6.0.0.rc1 → 6.0.0.rc2

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/lib/active_record/associations/builder/collection_association.rb +2 -2
  4. data/lib/active_record/associations/collection_proxy.rb +1 -1
  5. data/lib/active_record/associations/join_dependency.rb +10 -9
  6. data/lib/active_record/associations/join_dependency/join_association.rb +11 -2
  7. data/lib/active_record/associations/preloader/association.rb +3 -1
  8. data/lib/active_record/attribute_methods.rb +0 -51
  9. data/lib/active_record/attribute_methods/dirty.rb +6 -1
  10. data/lib/active_record/autosave_association.rb +1 -1
  11. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +93 -11
  12. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  13. data/lib/active_record/connection_adapters/abstract/quoting.rb +53 -0
  14. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -1
  15. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -7
  16. data/lib/active_record/connection_adapters/abstract/transaction.rb +12 -4
  17. data/lib/active_record/connection_adapters/abstract_adapter.rb +40 -20
  18. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +2 -2
  19. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  20. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +3 -1
  21. data/lib/active_record/connection_adapters/mysql2_adapter.rb +10 -1
  22. data/lib/active_record/connection_adapters/postgresql/quoting.rb +39 -2
  23. data/lib/active_record/connection_adapters/postgresql_adapter.rb +8 -1
  24. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +38 -2
  25. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +12 -2
  26. data/lib/active_record/connection_handling.rb +6 -2
  27. data/lib/active_record/database_configurations.rb +6 -6
  28. data/lib/active_record/gem_version.rb +1 -1
  29. data/lib/active_record/middleware/database_selector.rb +3 -3
  30. data/lib/active_record/middleware/database_selector/resolver.rb +2 -2
  31. data/lib/active_record/migration.rb +26 -23
  32. data/lib/active_record/railtie.rb +0 -1
  33. data/lib/active_record/railties/databases.rake +57 -23
  34. data/lib/active_record/reflection.rb +1 -1
  35. data/lib/active_record/relation/calculations.rb +1 -1
  36. data/lib/active_record/relation/finder_methods.rb +4 -2
  37. data/lib/active_record/relation/merger.rb +6 -2
  38. data/lib/active_record/relation/query_methods.rb +32 -32
  39. data/lib/active_record/sanitization.rb +30 -2
  40. data/lib/active_record/schema.rb +1 -1
  41. data/lib/active_record/schema_dumper.rb +5 -1
  42. data/lib/active_record/table_metadata.rb +6 -10
  43. data/lib/active_record/tasks/database_tasks.rb +41 -8
  44. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -1
  45. data/lib/active_record/timestamp.rb +26 -16
  46. data/lib/active_record/touch_later.rb +2 -0
  47. data/lib/active_record/transactions.rb +9 -10
  48. data/lib/active_record/type_caster/connection.rb +16 -10
  49. data/lib/arel/visitors/depth_first.rb +1 -1
  50. data/lib/arel/visitors/to_sql.rb +23 -26
  51. data/lib/arel/visitors/visitor.rb +9 -5
  52. metadata +8 -8
@@ -33,17 +33,17 @@ module ActiveRecord
33
33
  end
34
34
 
35
35
  def enable_query_cache!
36
- @query_cache_enabled[connection_cache_key(Thread.current)] = true
36
+ @query_cache_enabled[connection_cache_key(current_thread)] = true
37
37
  connection.enable_query_cache! if active_connection?
38
38
  end
39
39
 
40
40
  def disable_query_cache!
41
- @query_cache_enabled.delete connection_cache_key(Thread.current)
41
+ @query_cache_enabled.delete connection_cache_key(current_thread)
42
42
  connection.disable_query_cache! if active_connection?
43
43
  end
44
44
 
45
45
  def query_cache_enabled
46
- @query_cache_enabled[connection_cache_key(Thread.current)]
46
+ @query_cache_enabled[connection_cache_key(current_thread)]
47
47
  end
48
48
  end
49
49
 
@@ -142,6 +142,59 @@ module ActiveRecord
142
142
  value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
143
143
  end
144
144
 
145
+ def column_name_matcher # :nodoc:
146
+ COLUMN_NAME
147
+ end
148
+
149
+ def column_name_with_order_matcher # :nodoc:
150
+ COLUMN_NAME_WITH_ORDER
151
+ end
152
+
153
+ # Regexp for column names (with or without a table name prefix).
154
+ # Matches the following:
155
+ #
156
+ # "#{table_name}.#{column_name}"
157
+ # "#{column_name}"
158
+ COLUMN_NAME = /
159
+ \A
160
+ (
161
+ (?:
162
+ # table_name.column_name | function(one or no argument)
163
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
164
+ )
165
+ (?:\s+AS\s+\w+)?
166
+ )
167
+ (?:\s*,\s*\g<1>)*
168
+ \z
169
+ /ix
170
+
171
+ # Regexp for column names with order (with or without a table name prefix,
172
+ # with or without various order modifiers). Matches the following:
173
+ #
174
+ # "#{table_name}.#{column_name}"
175
+ # "#{table_name}.#{column_name} #{direction}"
176
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
177
+ # "#{table_name}.#{column_name} NULLS LAST"
178
+ # "#{column_name}"
179
+ # "#{column_name} #{direction}"
180
+ # "#{column_name} #{direction} NULLS FIRST"
181
+ # "#{column_name} NULLS LAST"
182
+ COLUMN_NAME_WITH_ORDER = /
183
+ \A
184
+ (
185
+ (?:
186
+ # table_name.column_name | function(one or no argument)
187
+ ((?:\w+\.)?\w+) | \w+\((?:|\g<2>)\)
188
+ )
189
+ (?:\s+ASC|\s+DESC)?
190
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
191
+ )
192
+ (?:\s*,\s*\g<1>)*
193
+ \z
194
+ /ix
195
+
196
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
197
+
145
198
  private
146
199
  def type_casted_binds(binds)
147
200
  if binds.first.is_a?(Array)
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  def column_spec_for_primary_key(column)
16
16
  return {} if default_primary_key?(column)
17
17
  spec = { id: schema_type(column).inspect }
18
- spec.merge!(prepare_column_options(column).except!(:null))
18
+ spec.merge!(prepare_column_options(column).except!(:null, :comment))
19
19
  spec[:default] ||= "nil" if explicit_primary_key_default?(column)
20
20
  spec
21
21
  end
@@ -302,7 +302,7 @@ module ActiveRecord
302
302
  if pk.is_a?(Array)
303
303
  td.primary_keys pk
304
304
  else
305
- td.primary_key pk, options.fetch(:id, :primary_key), options
305
+ td.primary_key pk, options.fetch(:id, :primary_key), options.except(:comment)
306
306
  end
307
307
  end
308
308
 
@@ -518,14 +518,15 @@ module ActiveRecord
518
518
  # Available options are (none of these exists by default):
519
519
  # * <tt>:limit</tt> -
520
520
  # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
521
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
521
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
522
522
  # This option is ignored by some backends.
523
523
  # * <tt>:default</tt> -
524
524
  # The column's default value. Use +nil+ for +NULL+.
525
525
  # * <tt>:null</tt> -
526
526
  # Allows or disallows +NULL+ values in the column.
527
527
  # * <tt>:precision</tt> -
528
- # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
528
+ # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
529
+ # <tt>:datetime</tt>, and <tt>:time</tt> columns.
529
530
  # * <tt>:scale</tt> -
530
531
  # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
531
532
  # * <tt>:collation</tt> -
@@ -1060,8 +1061,8 @@ module ActiveRecord
1060
1061
  options
1061
1062
  end
1062
1063
 
1063
- def dump_schema_information #:nodoc:
1064
- versions = ActiveRecord::SchemaMigration.all_versions
1064
+ def dump_schema_information # :nodoc:
1065
+ versions = schema_migration.all_versions
1065
1066
  insert_versions_sql(versions) if versions.any?
1066
1067
  end
1067
1068
 
@@ -1077,7 +1078,7 @@ module ActiveRecord
1077
1078
  end
1078
1079
 
1079
1080
  version = version.to_i
1080
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1081
+ sm_table = quote_table_name(schema_migration.table_name)
1081
1082
 
1082
1083
  migrated = migration_context.get_all_versions
1083
1084
  versions = migration_context.migrations.map(&:version)
@@ -1450,7 +1451,7 @@ module ActiveRecord
1450
1451
  end
1451
1452
 
1452
1453
  def insert_versions_sql(versions)
1453
- sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name)
1454
+ sm_table = quote_table_name(schema_migration.table_name)
1454
1455
 
1455
1456
  if versions.is_a?(Array)
1456
1457
  sql = +"INSERT INTO #{sm_table} (version) VALUES\n"
@@ -98,9 +98,13 @@ module ActiveRecord
98
98
  end
99
99
 
100
100
  def rollback_records
101
- ite = records.uniq
101
+ ite = records.uniq(&:object_id)
102
+ already_run_callbacks = {}
102
103
  while record = ite.shift
103
- record.rolledback!(force_restore_state: full_rollback?)
104
+ trigger_callbacks = record.trigger_transactional_callbacks?
105
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
106
+ already_run_callbacks[record] ||= trigger_callbacks
107
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
104
108
  end
105
109
  ensure
106
110
  ite.each do |i|
@@ -113,10 +117,14 @@ module ActiveRecord
113
117
  end
114
118
 
115
119
  def commit_records
116
- ite = records.uniq
120
+ ite = records.uniq(&:object_id)
121
+ already_run_callbacks = {}
117
122
  while record = ite.shift
118
123
  if @run_commit_callbacks
119
- record.committed!
124
+ trigger_callbacks = record.trigger_transactional_callbacks?
125
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
126
+ already_run_callbacks[record] ||= trigger_callbacks
127
+ record.committed!(should_run_callbacks: should_run_callbacks)
120
128
  else
121
129
  # if not running callbacks, only adds the record to the parent transaction
122
130
  connection.add_transaction_record(record)
@@ -78,7 +78,7 @@ module ActiveRecord
78
78
  SIMPLE_INT = /\A\d+\z/
79
79
 
80
80
  attr_accessor :pool
81
- attr_reader :schema_cache, :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes
81
+ attr_reader :visitor, :owner, :logger, :lock, :prepared_statements
82
82
  alias :in_use? :owner
83
83
 
84
84
  set_callback :checkin, :after, :enable_lazy_transactions!
@@ -106,6 +106,14 @@ module ActiveRecord
106
106
  Regexp.union(*parts)
107
107
  end
108
108
 
109
+ def self.quoted_column_names # :nodoc:
110
+ @quoted_column_names ||= {}
111
+ end
112
+
113
+ def self.quoted_table_names # :nodoc:
114
+ @quoted_table_names ||= {}
115
+ end
116
+
109
117
  def initialize(connection, logger = nil, config = {}) # :nodoc:
110
118
  super()
111
119
 
@@ -114,11 +122,8 @@ module ActiveRecord
114
122
  @instrumenter = ActiveSupport::Notifications.instrumenter
115
123
  @logger = logger
116
124
  @config = config
117
- @pool = nil
125
+ @pool = ActiveRecord::ConnectionAdapters::NullPool.new
118
126
  @idle_since = Concurrent.monotonic_time
119
- @schema_cache = SchemaCache.new self
120
- @quoted_column_names, @quoted_table_names = {}, {}
121
- @prevent_writes = false
122
127
  @visitor = arel_visitor
123
128
  @statements = build_statement_pool
124
129
  @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new
@@ -144,19 +149,7 @@ module ActiveRecord
144
149
  # Returns true if the connection is a replica, or if +prevent_writes+
145
150
  # is set to true.
146
151
  def preventing_writes?
147
- replica? || prevent_writes
148
- end
149
-
150
- # Prevent writing to the database regardless of role.
151
- #
152
- # In some cases you may want to prevent writes to the database
153
- # even if you are on a database that can write. `while_preventing_writes`
154
- # will prevent writes to the database for the duration of the block.
155
- def while_preventing_writes
156
- original, @prevent_writes = @prevent_writes, true
157
- yield
158
- ensure
159
- @prevent_writes = original
152
+ replica? || ActiveRecord::Base.connection_handler.prevent_writes
160
153
  end
161
154
 
162
155
  def migrations_paths # :nodoc:
@@ -164,7 +157,22 @@ module ActiveRecord
164
157
  end
165
158
 
166
159
  def migration_context # :nodoc:
167
- MigrationContext.new(migrations_paths)
160
+ MigrationContext.new(migrations_paths, schema_migration)
161
+ end
162
+
163
+ def schema_migration # :nodoc:
164
+ @schema_migration ||= begin
165
+ conn = self
166
+ spec_name = conn.pool.spec.name
167
+ name = "#{spec_name}::SchemaMigration"
168
+
169
+ Class.new(ActiveRecord::SchemaMigration) do
170
+ define_singleton_method(:name) { name }
171
+ define_singleton_method(:to_s) { name }
172
+
173
+ self.connection_specification_name = spec_name
174
+ end
175
+ end
168
176
  end
169
177
 
170
178
  class Version
@@ -206,9 +214,13 @@ module ActiveRecord
206
214
  @owner = Thread.current
207
215
  end
208
216
 
217
+ def schema_cache
218
+ @pool.get_schema_cache(self)
219
+ end
220
+
209
221
  def schema_cache=(cache)
210
222
  cache.connection = self
211
- @schema_cache = cache
223
+ @pool.set_schema_cache(cache)
212
224
  end
213
225
 
214
226
  # this method must only be called while holding connection pool's mutex
@@ -259,6 +271,11 @@ module ActiveRecord
259
271
  self.class::ADAPTER_NAME
260
272
  end
261
273
 
274
+ # Does the database for this adapter exist?
275
+ def self.database_exists?(config)
276
+ raise NotImplementedError
277
+ end
278
+
262
279
  # Does this adapter support DDL rollbacks in transactions? That is, would
263
280
  # CREATE TABLE or ALTER TABLE get rolled back by a transaction?
264
281
  def supports_ddl_transactions?
@@ -487,6 +504,9 @@ module ActiveRecord
487
504
  #
488
505
  # Prevent @connection's finalizer from touching the socket, or
489
506
  # otherwise communicating with its server, when it is collected.
507
+ if schema_cache.connection == self
508
+ schema_cache.connection = nil
509
+ end
490
510
  end
491
511
 
492
512
  # Reset the state of this connection, directing the DBMS to clear
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module DetermineIfPreparableVisitor
6
6
  attr_accessor :preparable
7
7
 
8
- def accept(*)
8
+ def accept(object, collector)
9
9
  @preparable = true
10
10
  super
11
11
  end
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  super
21
21
  end
22
22
 
23
- def visit_Arel_Nodes_SqlLiteral(*)
23
+ def visit_Arel_Nodes_SqlLiteral(o, collector)
24
24
  @preparable = false
25
25
  super
26
26
  end
@@ -5,11 +5,11 @@ module ActiveRecord
5
5
  module MySQL
6
6
  module Quoting # :nodoc:
7
7
  def quote_column_name(name)
8
- @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
8
+ self.class.quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
9
9
  end
10
10
 
11
11
  def quote_table_name(name)
12
- @quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
12
+ self.class.quoted_table_names[name] ||= super.gsub(".", "`.`").freeze
13
13
  end
14
14
 
15
15
  def unquoted_true
@@ -32,12 +32,49 @@ module ActiveRecord
32
32
  "x'#{value.hex}'"
33
33
  end
34
34
 
35
- def _type_cast(value)
36
- case value
37
- when Date, Time then value
38
- else super
39
- end
35
+ def column_name_matcher
36
+ COLUMN_NAME
37
+ end
38
+
39
+ def column_name_with_order_matcher
40
+ COLUMN_NAME_WITH_ORDER
40
41
  end
42
+
43
+ COLUMN_NAME = /
44
+ \A
45
+ (
46
+ (?:
47
+ # `table_name`.`column_name` | function(one or no argument)
48
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
49
+ )
50
+ (?:\s+AS\s+(?:\w+|`\w+`))?
51
+ )
52
+ (?:\s*,\s*\g<1>)*
53
+ \z
54
+ /ix
55
+
56
+ COLUMN_NAME_WITH_ORDER = /
57
+ \A
58
+ (
59
+ (?:
60
+ # `table_name`.`column_name` | function(one or no argument)
61
+ ((?:\w+\.|`\w+`\.)?(?:\w+|`\w+`)) | \w+\((?:|\g<2>)\)
62
+ )
63
+ (?:\s+ASC|\s+DESC)?
64
+ )
65
+ (?:\s*,\s*\g<1>)*
66
+ \z
67
+ /ix
68
+
69
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
70
+
71
+ private
72
+ def _type_cast(value)
73
+ case value
74
+ when Date, Time then value
75
+ else super
76
+ end
77
+ end
41
78
  end
42
79
  end
43
80
  end
@@ -41,13 +41,15 @@ module ActiveRecord
41
41
  case column.sql_type
42
42
  when /\Atimestamp\b/
43
43
  :timestamp
44
+ when /\A(?:enum|set)\b/
45
+ column.sql_type
44
46
  else
45
47
  super
46
48
  end
47
49
  end
48
50
 
49
51
  def schema_limit(column)
50
- super unless /\A(?:tiny|medium|long)?(?:text|blob)/.match?(column.sql_type)
52
+ super unless /\A(?:enum|set|(?:tiny|medium|long)?(?:text|blob))\b/.match?(column.sql_type)
51
53
  end
52
54
 
53
55
  def schema_precision(column)
@@ -8,6 +8,8 @@ require "mysql2"
8
8
 
9
9
  module ActiveRecord
10
10
  module ConnectionHandling # :nodoc:
11
+ ER_BAD_DB_ERROR = 1049
12
+
11
13
  # Establishes a connection to the database that's used by all Active Record objects.
12
14
  def mysql2_connection(config)
13
15
  config = config.symbolize_keys
@@ -22,7 +24,7 @@ module ActiveRecord
22
24
  client = Mysql2::Client.new(config)
23
25
  ConnectionAdapters::Mysql2Adapter.new(client, logger, nil, config)
24
26
  rescue Mysql2::Error => error
25
- if error.message.include?("Unknown database")
27
+ if error.error_number == ER_BAD_DB_ERROR
26
28
  raise ActiveRecord::NoDatabaseError
27
29
  else
28
30
  raise
@@ -42,6 +44,12 @@ module ActiveRecord
42
44
  configure_connection
43
45
  end
44
46
 
47
+ def self.database_exists?(config)
48
+ !!ActiveRecord::Base.mysql2_connection(config)
49
+ rescue ActiveRecord::NoDatabaseError
50
+ false
51
+ end
52
+
45
53
  def supports_json?
46
54
  !mariadb? && database_version >= "5.7.8"
47
55
  end
@@ -109,6 +117,7 @@ module ActiveRecord
109
117
  end
110
118
 
111
119
  def discard! # :nodoc:
120
+ super
112
121
  @connection.automatic_close = false
113
122
  @connection = nil
114
123
  end
@@ -30,7 +30,7 @@ module ActiveRecord
30
30
  # - "schema.name".table_name
31
31
  # - "schema.name"."table.name"
32
32
  def quote_table_name(name) # :nodoc:
33
- @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
33
+ self.class.quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted.freeze
34
34
  end
35
35
 
36
36
  # Quotes schema names for use in SQL queries.
@@ -44,7 +44,7 @@ module ActiveRecord
44
44
 
45
45
  # Quotes column names for use in SQL queries.
46
46
  def quote_column_name(name) # :nodoc:
47
- @quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
47
+ self.class.quoted_column_names[name] ||= PG::Connection.quote_ident(super).freeze
48
48
  end
49
49
 
50
50
  # Quote date/time values for use in SQL input.
@@ -78,6 +78,43 @@ module ActiveRecord
78
78
  type_map.lookup(column.oid, column.fmod, column.sql_type)
79
79
  end
80
80
 
81
+ def column_name_matcher
82
+ COLUMN_NAME
83
+ end
84
+
85
+ def column_name_with_order_matcher
86
+ COLUMN_NAME_WITH_ORDER
87
+ end
88
+
89
+ COLUMN_NAME = /
90
+ \A
91
+ (
92
+ (?:
93
+ # "table_name"."column_name"::type_name | function(one or no argument)::type_name
94
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
95
+ )
96
+ (?:\s+AS\s+(?:\w+|"\w+"))?
97
+ )
98
+ (?:\s*,\s*\g<1>)*
99
+ \z
100
+ /ix
101
+
102
+ COLUMN_NAME_WITH_ORDER = /
103
+ \A
104
+ (
105
+ (?:
106
+ # "table_name"."column_name"::type_name | function(one or no argument)::type_name
107
+ ((?:\w+\.|"\w+"\.)?(?:\w+|"\w+")(?:::\w+)?) | \w+\((?:|\g<2>)\)(?:::\w+)?
108
+ )
109
+ (?:\s+ASC|\s+DESC)?
110
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
111
+ )
112
+ (?:\s*,\s*\g<1>)*
113
+ \z
114
+ /ix
115
+
116
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
117
+
81
118
  private
82
119
  def lookup_cast_type(sql_type)
83
120
  super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)