activerecord 7.0.0 → 7.0.4

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +357 -0
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record/associations/collection_association.rb +1 -2
  5. data/lib/active_record/associations/collection_proxy.rb +2 -2
  6. data/lib/active_record/associations/has_many_association.rb +7 -4
  7. data/lib/active_record/associations/join_dependency.rb +17 -13
  8. data/lib/active_record/associations.rb +38 -17
  9. data/lib/active_record/attribute_methods/serialization.rb +34 -50
  10. data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
  11. data/lib/active_record/attribute_methods.rb +2 -2
  12. data/lib/active_record/autosave_association.rb +2 -2
  13. data/lib/active_record/base.rb +3 -3
  14. data/lib/active_record/coders/yaml_column.rb +10 -2
  15. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -1
  16. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  17. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -4
  18. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  19. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -7
  20. data/lib/active_record/connection_adapters/abstract_adapter.rb +5 -5
  21. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -2
  22. data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
  23. data/lib/active_record/connection_adapters/mysql/quoting.rb +3 -1
  24. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
  25. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
  26. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  27. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
  28. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  29. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -1
  30. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +10 -3
  31. data/lib/active_record/connection_adapters/postgresql_adapter.rb +6 -5
  32. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
  33. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -14
  34. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +35 -2
  35. data/lib/active_record/connection_handling.rb +2 -2
  36. data/lib/active_record/core.rb +3 -3
  37. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  38. data/lib/active_record/database_configurations.rb +1 -1
  39. data/lib/active_record/delegated_type.rb +1 -1
  40. data/lib/active_record/encryption/configurable.rb +9 -3
  41. data/lib/active_record/encryption/contexts.rb +3 -3
  42. data/lib/active_record/encryption/derived_secret_key_provider.rb +1 -1
  43. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  44. data/lib/active_record/encryption/encryptable_record.rb +2 -4
  45. data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
  46. data/lib/active_record/encryption/encryptor.rb +7 -7
  47. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  48. data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
  49. data/lib/active_record/encryption/message.rb +1 -1
  50. data/lib/active_record/encryption/properties.rb +1 -1
  51. data/lib/active_record/encryption/scheme.rb +1 -1
  52. data/lib/active_record/enum.rb +1 -1
  53. data/lib/active_record/fixtures.rb +5 -5
  54. data/lib/active_record/gem_version.rb +2 -2
  55. data/lib/active_record/integration.rb +2 -2
  56. data/lib/active_record/locking/pessimistic.rb +3 -3
  57. data/lib/active_record/log_subscriber.rb +10 -5
  58. data/lib/active_record/middleware/database_selector.rb +13 -6
  59. data/lib/active_record/middleware/shard_selector.rb +4 -4
  60. data/lib/active_record/migration/command_recorder.rb +3 -3
  61. data/lib/active_record/migration/compatibility.rb +10 -7
  62. data/lib/active_record/migration.rb +6 -5
  63. data/lib/active_record/model_schema.rb +22 -10
  64. data/lib/active_record/persistence.rb +9 -8
  65. data/lib/active_record/querying.rb +1 -1
  66. data/lib/active_record/railtie.rb +22 -18
  67. data/lib/active_record/railties/databases.rake +16 -11
  68. data/lib/active_record/reflection.rb +7 -1
  69. data/lib/active_record/relation/batches.rb +3 -3
  70. data/lib/active_record/relation/calculations.rb +3 -2
  71. data/lib/active_record/relation/delegation.rb +1 -1
  72. data/lib/active_record/relation/query_methods.rb +46 -11
  73. data/lib/active_record/relation.rb +22 -6
  74. data/lib/active_record/sanitization.rb +6 -5
  75. data/lib/active_record/schema.rb +38 -23
  76. data/lib/active_record/schema_dumper.rb +15 -16
  77. data/lib/active_record/scoping/default.rb +5 -7
  78. data/lib/active_record/serialization.rb +5 -0
  79. data/lib/active_record/signed_id.rb +2 -2
  80. data/lib/active_record/store.rb +7 -2
  81. data/lib/active_record/tasks/database_tasks.rb +32 -23
  82. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -2
  83. data/lib/active_record/test_fixtures.rb +12 -5
  84. data/lib/active_record/translation.rb +1 -1
  85. data/lib/active_record/validations/associated.rb +3 -3
  86. data/lib/active_record/validations/presence.rb +2 -2
  87. data/lib/active_record/validations/uniqueness.rb +3 -3
  88. data/lib/active_record/version.rb +1 -1
  89. data/lib/active_record.rb +15 -1
  90. metadata +13 -13
@@ -24,38 +24,6 @@ module ActiveRecord
24
24
  # The serialization format may be YAML, JSON, or any custom format using a
25
25
  # custom coder class.
26
26
  #
27
- # === Serialization formats
28
- #
29
- # serialize attr_name [, class_name_or_coder]
30
- #
31
- # | | database storage |
32
- # class_name_or_coder | attribute read/write type | serialized | NULL |
33
- # ---------------------+---------------------------+------------+--------+
34
- # <not given> | any value that supports | YAML | |
35
- # | .to_yaml | | |
36
- # | | | |
37
- # Array | Array ** | YAML | [] |
38
- # | | | |
39
- # Hash | Hash ** | YAML | {} |
40
- # | | | |
41
- # JSON | any value that supports | JSON | |
42
- # | .to_json | | |
43
- # | | | |
44
- # <custom coder class> | any value supported by | custom | custom |
45
- # | the custom coder class | | |
46
- #
47
- # ** If +class_name_or_coder+ is +Array+ or +Hash+, values retrieved will
48
- # always be of that type, and any value assigned must be of that type or
49
- # +SerializationTypeMismatch+ will be raised.
50
- #
51
- # ==== Custom coders
52
- # A custom coder class or module may be given. This must have +self.load+
53
- # and +self.dump+ class/module methods. <tt>self.dump(object)</tt> will be called
54
- # to serialize an object and should return the serialized value to be
55
- # stored in the database (+nil+ to store as +NULL+). <tt>self.load(string)</tt>
56
- # will be called to reverse the process and load (unserialize) from the
57
- # database.
58
- #
59
27
  # Keep in mind that database adapters handle certain serialization tasks
60
28
  # for you. For instance: +json+ and +jsonb+ types in PostgreSQL will be
61
29
  # converted between JSON object/array syntax and Ruby +Hash+ or +Array+
@@ -67,55 +35,71 @@ module ActiveRecord
67
35
  #
68
36
  # ==== Parameters
69
37
  #
70
- # * +attr_name+ - The field name that should be serialized.
71
- # * +class_name_or_coder+ - Optional, may be be +Array+ or +Hash+ or
72
- # +JSON+ or a custom coder class or module which responds to +.load+
73
- # and +.dump+. See table above.
38
+ # * +attr_name+ - The name of the attribute to serialize.
39
+ # * +class_name_or_coder+ - Optional. May be one of the following:
40
+ # * <em>default</em> - The attribute value will be serialized as YAML.
41
+ # The attribute value must respond to +to_yaml+.
42
+ # * +Array+ - The attribute value will be serialized as YAML, but an
43
+ # empty +Array+ will be serialized as +NULL+. The attribute value
44
+ # must be an +Array+.
45
+ # * +Hash+ - The attribute value will be serialized as YAML, but an
46
+ # empty +Hash+ will be serialized as +NULL+. The attribute value
47
+ # must be a +Hash+.
48
+ # * +JSON+ - The attribute value will be serialized as JSON. The
49
+ # attribute value must respond to +to_json+.
50
+ # * <em>custom coder</em> - The attribute value will be serialized
51
+ # using the coder's <tt>dump(value)</tt> method, and will be
52
+ # deserialized using the coder's <tt>load(string)</tt> method. The
53
+ # +dump+ method may return +nil+ to serialize the value as +NULL+.
74
54
  #
75
55
  # ==== Options
76
56
  #
77
- # +default+ The default value to use when no value is provided. If this option
78
- # is not passed, the previous default value (if any) will be used.
79
- # Otherwise, the default will be +nil+.
57
+ # * +:default+ - The default value to use when no value is provided. If
58
+ # this option is not passed, the previous default value (if any) will
59
+ # be used. Otherwise, the default will be +nil+.
60
+ #
61
+ # ==== Examples
80
62
  #
81
- # ==== Example
63
+ # ===== Serialize the +preferences+ attribute using YAML
82
64
  #
83
- # # Serialize a preferences attribute using YAML coder.
84
65
  # class User < ActiveRecord::Base
85
66
  # serialize :preferences
86
67
  # end
87
68
  #
88
- # # Serialize preferences using JSON as coder.
69
+ # ===== Serialize the +preferences+ attribute using JSON
70
+ #
89
71
  # class User < ActiveRecord::Base
90
72
  # serialize :preferences, JSON
91
73
  # end
92
74
  #
93
- # # Serialize preferences as Hash using YAML coder.
75
+ # ===== Serialize the +preferences+ +Hash+ using YAML
76
+ #
94
77
  # class User < ActiveRecord::Base
95
78
  # serialize :preferences, Hash
96
79
  # end
97
80
  #
98
- # # Serialize preferences using a custom coder.
81
+ # ===== Serialize the +preferences+ attribute using a custom coder
82
+ #
99
83
  # class Rot13JSON
100
84
  # def self.rot13(string)
101
85
  # string.tr("a-zA-Z", "n-za-mN-ZA-M")
102
86
  # end
103
87
  #
104
- # # returns serialized string that will be stored in the database
105
- # def self.dump(object)
106
- # ActiveSupport::JSON.encode(object).rot13
88
+ # # Serializes an attribute value to a string that will be stored in the database.
89
+ # def self.dump(value)
90
+ # rot13(ActiveSupport::JSON.dump(value))
107
91
  # end
108
92
  #
109
- # # reverses the above, turning the serialized string from the database
110
- # # back into its original value
93
+ # # Deserializes a string from the database to an attribute value.
111
94
  # def self.load(string)
112
- # ActiveSupport::JSON.decode(string.rot13)
95
+ # ActiveSupport::JSON.load(rot13(string))
113
96
  # end
114
97
  # end
115
98
  #
116
99
  # class User < ActiveRecord::Base
117
100
  # serialize :preferences, Rot13JSON
118
101
  # end
102
+ #
119
103
  def serialize(attr_name, class_name_or_coder = Object, **options)
120
104
  # When ::JSON is used, force it to go through the Active Support JSON encoder
121
105
  # to ensure special objects (e.g. Active Record models) are dumped correctly
@@ -19,6 +19,8 @@ module ActiveRecord
19
19
 
20
20
  if value.is_a?(Hash)
21
21
  set_time_zone_without_conversion(super)
22
+ elsif value.is_a?(Range)
23
+ Range.new(user_input_in_time_zone(value.begin), user_input_in_time_zone(value.end), value.exclude_end?)
22
24
  elsif value.respond_to?(:in_time_zone)
23
25
  begin
24
26
  super(user_input_in_time_zone(value)) || super
@@ -40,6 +42,8 @@ module ActiveRecord
40
42
  value.in_time_zone
41
43
  elsif value.respond_to?(:infinite?) && value.infinite?
42
44
  value
45
+ elsif value.is_a?(Range)
46
+ Range.new(convert_time_to_time_zone(value.begin), convert_time_to_time_zone(value.end), value.exclude_end?)
43
47
  else
44
48
  map_avoiding_infinite_recursion(value) { |v| convert_time_to_time_zone(v) }
45
49
  end
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
  super
98
98
  else
99
99
  # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass
100
- # defines its own attribute method, then we don't want to overwrite that.
100
+ # defines its own attribute method, then we don't want to override that.
101
101
  defined = method_defined_within?(method_name, superclass, Base) &&
102
102
  ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods)
103
103
  defined || super
@@ -413,7 +413,7 @@ module ActiveRecord
413
413
  inspected_value = if value.is_a?(String) && value.length > 50
414
414
  "#{value[0, 50]}...".inspect
415
415
  elsif value.is_a?(Date) || value.is_a?(Time)
416
- %("#{value.to_formatted_s(:inspect)}")
416
+ %("#{value.to_fs(:inspect)}")
417
417
  else
418
418
  value.inspect
419
419
  end
@@ -446,7 +446,7 @@ module ActiveRecord
446
446
  elsif autosave != false
447
447
  key = reflection.options[:primary_key] ? public_send(reflection.options[:primary_key]) : id
448
448
 
449
- if (autosave && record.changed_for_autosave?) || record_changed?(reflection, record, key)
449
+ if (autosave && record.changed_for_autosave?) || _record_changed?(reflection, record, key)
450
450
  unless reflection.through_reflection
451
451
  record[reflection.foreign_key] = key
452
452
  association.set_inverse_instance(record)
@@ -461,7 +461,7 @@ module ActiveRecord
461
461
  end
462
462
 
463
463
  # If the record is new or it has changed, returns true.
464
- def record_changed?(reflection, record, key)
464
+ def _record_changed?(reflection, record, key)
465
465
  record.new_record? ||
466
466
  association_foreign_key_changed?(reflection, record, key) ||
467
467
  record.will_save_change_to_attribute?(reflection.foreign_key)
@@ -137,7 +137,7 @@ module ActiveRecord # :nodoc:
137
137
  # anonymous = User.new(name: "")
138
138
  # anonymous.name? # => false
139
139
  #
140
- # Query methods will also respect any overwrites of default accessors:
140
+ # Query methods will also respect any overrides of default accessors:
141
141
  #
142
142
  # class User
143
143
  # # Has admin boolean column
@@ -151,8 +151,8 @@ module ActiveRecord # :nodoc:
151
151
  # user.read_attribute(:admin) # => true, gets the column value
152
152
  # user[:admin] # => true, also gets the column value
153
153
  #
154
- # user.admin # => false, due to the getter overwrite
155
- # user.admin? # => false, due to the getter overwrite
154
+ # user.admin # => false, due to the getter override
155
+ # user.admin? # => false, due to the getter override
156
156
  #
157
157
  # == Accessing attributes before they have been typecasted
158
158
  #
@@ -47,11 +47,19 @@ module ActiveRecord
47
47
 
48
48
  if YAML.respond_to?(:unsafe_load)
49
49
  def yaml_load(payload)
50
- YAML.unsafe_load(payload)
50
+ if ActiveRecord.use_yaml_unsafe_load
51
+ YAML.unsafe_load(payload)
52
+ else
53
+ YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
54
+ end
51
55
  end
52
56
  else
53
57
  def yaml_load(payload)
54
- YAML.load(payload)
58
+ if ActiveRecord.use_yaml_unsafe_load
59
+ YAML.load(payload)
60
+ else
61
+ YAML.safe_load(payload, permitted_classes: ActiveRecord.yaml_column_permitted_classes, aliases: true)
62
+ end
55
63
  end
56
64
  end
57
65
  end
@@ -40,7 +40,7 @@ module ActiveRecord
40
40
  # but the Book model connects to a separate database called "library_db"
41
41
  # (this can even be a database on a different machine).
42
42
  #
43
- # Book, ScaryBook and GoodBook will all use the same connection pool to
43
+ # Book, ScaryBook, and GoodBook will all use the same connection pool to
44
44
  # "library_db" while Author, BankAccount, and any other models you create
45
45
  # will use the default connection pool to "my_application".
46
46
  #
@@ -128,7 +128,7 @@ module ActiveRecord
128
128
  end
129
129
  end
130
130
 
131
- result = value.to_formatted_s(:db)
131
+ result = value.to_fs(:db)
132
132
  if value.respond_to?(:usec) && value.usec > 0
133
133
  result << "." << sprintf("%06d", value.usec)
134
134
  else
@@ -202,6 +202,10 @@ module ActiveRecord
202
202
 
203
203
  def index_options(table_name)
204
204
  index_options = as_options(index)
205
+
206
+ # legacy reference index names are used on versions 6.0 and earlier
207
+ return index_options if options[:_uses_legacy_reference_index_name]
208
+
205
209
  index_options[:name] ||= polymorphic_index_name(table_name) if polymorphic
206
210
  index_options
207
211
  end
@@ -810,8 +814,8 @@ module ActiveRecord
810
814
  # t.check_constraint("price > 0", name: "price_check")
811
815
  #
812
816
  # See {connection.add_check_constraint}[rdoc-ref:SchemaStatements#add_check_constraint]
813
- def check_constraint(*args)
814
- @base.add_check_constraint(name, *args)
817
+ def check_constraint(*args, **options)
818
+ @base.add_check_constraint(name, *args, **options)
815
819
  end
816
820
 
817
821
  # Removes the given check constraint from the table.
@@ -819,8 +823,8 @@ module ActiveRecord
819
823
  # t.remove_check_constraint(name: "price_check")
820
824
  #
821
825
  # See {connection.remove_check_constraint}[rdoc-ref:SchemaStatements#remove_check_constraint]
822
- def remove_check_constraint(*args)
823
- @base.remove_check_constraint(name, *args)
826
+ def remove_check_constraint(*args, **options)
827
+ @base.remove_check_constraint(name, *args, **options)
824
828
  end
825
829
  end
826
830
  end
@@ -3,6 +3,8 @@
3
3
  module ActiveRecord
4
4
  module ConnectionAdapters # :nodoc:
5
5
  class SchemaDumper < SchemaDumper # :nodoc:
6
+ DEFAULT_DATETIME_PRECISION = 6 # :nodoc:
7
+
6
8
  def self.create(connection, options)
7
9
  new(connection, options)
8
10
  end
@@ -63,7 +65,18 @@ module ActiveRecord
63
65
  end
64
66
 
65
67
  def schema_precision(column)
66
- column.precision.inspect if column.precision
68
+ if column.type == :datetime
69
+ case column.precision
70
+ when nil
71
+ "nil"
72
+ when DEFAULT_DATETIME_PRECISION
73
+ nil
74
+ else
75
+ column.precision.inspect
76
+ end
77
+ elsif column.precision
78
+ column.precision.inspect
79
+ end
67
80
  end
68
81
 
69
82
  def schema_scale(column)
@@ -1071,9 +1071,9 @@ module ActiveRecord
1071
1071
  # [<tt>:name</tt>]
1072
1072
  # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
1073
1073
  # [<tt>:on_delete</tt>]
1074
- # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1074
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1075
1075
  # [<tt>:on_update</tt>]
1076
- # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
1076
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+, and +:restrict+
1077
1077
  # [<tt>:if_not_exists</tt>]
1078
1078
  # Specifies if the foreign key already exists to not try to re-add it. This will avoid
1079
1079
  # duplicate column errors.
@@ -1125,7 +1125,7 @@ module ActiveRecord
1125
1125
  # The name of the table that contains the referenced primary key.
1126
1126
  def remove_foreign_key(from_table, to_table = nil, **options)
1127
1127
  return unless supports_foreign_keys?
1128
- return if options[:if_exists] == true && !foreign_key_exists?(from_table, to_table)
1128
+ return if options.delete(:if_exists) == true && !foreign_key_exists?(from_table, to_table)
1129
1129
 
1130
1130
  fk_name_to_delete = foreign_key_for!(from_table, to_table: to_table, **options).name
1131
1131
 
@@ -1438,7 +1438,7 @@ module ActiveRecord
1438
1438
 
1439
1439
  checks = []
1440
1440
 
1441
- if !options.key?(:name) && column_name.is_a?(String) && /\W/.match?(column_name)
1441
+ if !options.key?(:name) && expression_column_name?(column_name)
1442
1442
  options[:name] = index_name(table_name, column_name)
1443
1443
  column_names = []
1444
1444
  else
@@ -1447,7 +1447,7 @@ module ActiveRecord
1447
1447
 
1448
1448
  checks << lambda { |i| i.name == options[:name].to_s } if options.key?(:name)
1449
1449
 
1450
- if column_names.present?
1450
+ if column_names.present? && !(options.key?(:name) && expression_column_name?(column_names))
1451
1451
  checks << lambda { |i| index_name(table_name, i.columns) == index_name(table_name, column_names) }
1452
1452
  end
1453
1453
 
@@ -1515,7 +1515,7 @@ module ActiveRecord
1515
1515
  end
1516
1516
 
1517
1517
  def index_column_names(column_names)
1518
- if column_names.is_a?(String) && /\W/.match?(column_names)
1518
+ if expression_column_name?(column_names)
1519
1519
  column_names
1520
1520
  else
1521
1521
  Array(column_names)
@@ -1523,13 +1523,18 @@ module ActiveRecord
1523
1523
  end
1524
1524
 
1525
1525
  def index_name_options(column_names)
1526
- if column_names.is_a?(String) && /\W/.match?(column_names)
1526
+ if expression_column_name?(column_names)
1527
1527
  column_names = column_names.scan(/\w+/).join("_")
1528
1528
  end
1529
1529
 
1530
1530
  { column: column_names }
1531
1531
  end
1532
1532
 
1533
+ # Try to identify whether the given column name is an expression
1534
+ def expression_column_name?(column_name)
1535
+ column_name.is_a?(String) && /\W/.match?(column_name)
1536
+ end
1537
+
1533
1538
  def strip_table_name_prefix_and_suffix(table_name)
1534
1539
  prefix = Base.table_name_prefix
1535
1540
  suffix = Base.table_name_suffix
@@ -36,7 +36,7 @@ module ActiveRecord
36
36
  include Savepoints
37
37
 
38
38
  SIMPLE_INT = /\A\d+\z/
39
- COMMENT_REGEX = %r{(?:--.*\n)*|/\*(?:[^*]|\*[^/])*\*/}m
39
+ COMMENT_REGEX = %r{(?:--.*\n)|/\*(?:[^*]|\*[^/])*\*/}m
40
40
 
41
41
  attr_accessor :pool
42
42
  attr_reader :visitor, :owner, :logger, :lock
@@ -224,14 +224,14 @@ module ActiveRecord
224
224
  @pool.connection_class
225
225
  end
226
226
 
227
- # The role (ie :writing) for the current connection. In a
228
- # non-multi role application, `:writing` is returned.
227
+ # The role (e.g. +:writing+) for the current connection. In a
228
+ # non-multi role application, +:writing+ is returned.
229
229
  def role
230
230
  @pool.role
231
231
  end
232
232
 
233
- # The shard (ie :default) for the current connection. In
234
- # a non-sharded application, `:default` is returned.
233
+ # The shard (e.g. +:default+) for the current connection. In
234
+ # a non-sharded application, +:default+ is returned.
235
235
  def shard
236
236
  @pool.shard
237
237
  end
@@ -139,7 +139,7 @@ module ActiveRecord
139
139
  end
140
140
 
141
141
  def field_ordered_value(column, values) # :nodoc:
142
- field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse])
142
+ field = Arel::Nodes::NamedFunction.new("FIELD", [column, values.reverse.map { |value| Arel::Nodes.build_quoted(value) }])
143
143
  Arel::Nodes::Descending.new(field)
144
144
  end
145
145
 
@@ -197,7 +197,7 @@ module ActiveRecord
197
197
 
198
198
  # Executes the SQL statement in the context of this connection.
199
199
  def execute(sql, name = nil, async: false)
200
- raw_execute(sql, name, async)
200
+ raw_execute(sql, name, async: async)
201
201
  end
202
202
 
203
203
  # Mysql2Adapter doesn't have to free a result after using it, but we use this method
@@ -711,6 +711,14 @@ module ActiveRecord
711
711
  options[:comment] = column.comment
712
712
  end
713
713
 
714
+ unless options.key?(:collation)
715
+ options[:collation] = column.collation
716
+ end
717
+
718
+ unless options.key?(:auto_increment)
719
+ options[:auto_increment] = column.auto_increment?
720
+ end
721
+
714
722
  td = create_table_definition(table_name)
715
723
  cd = td.new_column_definition(column.name, type, **options)
716
724
  schema_creation.accept(ChangeColumnDefinition.new(cd, column.name))
@@ -100,8 +100,8 @@ module ActiveRecord
100
100
  statements = statements.map { |sql| transform_query(sql) }
101
101
  combine_multi_statements(statements).each do |statement|
102
102
  raw_execute(statement, name)
103
+ @connection.abandon_results!
103
104
  end
104
- @connection.abandon_results!
105
105
  end
106
106
 
107
107
  def default_insert_value(column)
@@ -8,7 +8,9 @@ module ActiveRecord
8
8
  module Quoting # :nodoc:
9
9
  def quote_bound_value(value)
10
10
  case value
11
- when Numeric
11
+ when Rational
12
+ quote(value.to_f.to_s)
13
+ when Numeric, ActiveSupport::Duration
12
14
  quote(value.to_s)
13
15
  when BigDecimal
14
16
  quote(value.to_s("F"))
@@ -53,7 +53,13 @@ module ActiveRecord
53
53
  end
54
54
 
55
55
  def schema_precision(column)
56
- super unless /\A(?:date)?time(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
56
+ if /\Atime(?:stamp)?\b/.match?(column.sql_type) && column.precision == 0
57
+ nil
58
+ elsif column.type == :datetime
59
+ column.precision == 0 ? "nil" : super
60
+ else
61
+ super
62
+ end
57
63
  end
58
64
 
59
65
  def schema_collation(column)
@@ -158,18 +158,37 @@ module ActiveRecord
158
158
  MySQL::TableDefinition.new(self, name, **options)
159
159
  end
160
160
 
161
+ def default_type(table_name, field_name)
162
+ match = create_table_info(table_name)&.match(/`#{field_name}` (.+) DEFAULT ('|\d+|[A-z]+)/)
163
+ default_pre = match[2] if match
164
+
165
+ if default_pre == "'"
166
+ :string
167
+ elsif default_pre&.match?(/^\d+$/)
168
+ :integer
169
+ elsif default_pre&.match?(/^[A-z]+$/)
170
+ :function
171
+ end
172
+ end
173
+
161
174
  def new_column_from_field(table_name, field)
175
+ field_name = field.fetch(:Field)
162
176
  type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
163
177
  default, default_function = field[:Default], nil
164
178
 
165
179
  if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
180
+ default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[:Extra])
166
181
  default, default_function = nil, default
167
182
  elsif type_metadata.extra == "DEFAULT_GENERATED"
168
183
  default = +"(#{default})" unless default.start_with?("(")
169
184
  default, default_function = nil, default
170
- elsif type_metadata.type == :text && default
185
+ elsif type_metadata.type == :text && default&.start_with?("'")
171
186
  # strip and unescape quotes
172
187
  default = default[1...-1].gsub("\\'", "'")
188
+ elsif default&.match?(/\A\d/)
189
+ # Its a number so we can skip the query to check if it is a function
190
+ elsif default && default_type(table_name, field_name) == :function
191
+ default, default_function = nil, default
173
192
  end
174
193
 
175
194
  MySQL::Column.new(
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  raise(ArgumentError, ERROR % scanner.string.inspect)
27
27
  end
28
28
 
29
- unless key = scanner.scan_until(/(?<!\\)(?=")/)
29
+ unless key = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
30
30
  raise(ArgumentError, ERROR % scanner.string.inspect)
31
31
  end
32
32
 
@@ -41,7 +41,7 @@ module ActiveRecord
41
41
  raise(ArgumentError, ERROR % scanner.string.inspect)
42
42
  end
43
43
 
44
- unless value = scanner.scan_until(/(?<!\\)(?=")/)
44
+ unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
45
45
  raise(ArgumentError, ERROR % scanner.string.inspect)
46
46
  end
47
47
 
@@ -10,6 +10,8 @@ module ActiveRecord
10
10
  end
11
11
 
12
12
  def cast_value(value)
13
+ return if value.blank?
14
+
13
15
  time = super
14
16
  return time if time.is_a?(ActiveSupport::TimeWithZone)
15
17
 
@@ -43,7 +43,7 @@ module ActiveRecord
43
43
 
44
44
  # Quotes strings for use in SQL input.
45
45
  def quote_string(s) # :nodoc:
46
- PG::Connection.escape(s)
46
+ @connection.escape(s)
47
47
  end
48
48
 
49
49
  # Checks the following cases:
@@ -45,8 +45,10 @@ Rails needs superuser privileges to disable referential integrity.
45
45
  BEGIN
46
46
  FOR r IN (
47
47
  SELECT FORMAT(
48
- 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I''; ALTER TABLE %I VALIDATE CONSTRAINT %I;',
48
+ 'UPDATE pg_constraint SET convalidated=false WHERE conname = ''%I'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
49
49
  constraint_name,
50
+ table_schema,
51
+ table_schema,
50
52
  table_name,
51
53
  constraint_name
52
54
  ) AS constraint_check
@@ -525,11 +525,13 @@ module ActiveRecord
525
525
  scope = quoted_scope(table_name)
526
526
 
527
527
  check_info = exec_query(<<-SQL, "SCHEMA")
528
- SELECT conname, pg_get_constraintdef(c.oid) AS constraintdef, c.convalidated AS valid
528
+ SELECT conname, pg_get_constraintdef(c.oid, true) AS constraintdef, c.convalidated AS valid
529
529
  FROM pg_constraint c
530
530
  JOIN pg_class t ON c.conrelid = t.oid
531
+ JOIN pg_namespace n ON n.oid = c.connamespace
531
532
  WHERE c.contype = 'c'
532
533
  AND t.relname = #{scope[:name]}
534
+ AND n.nspname = #{scope[:schema]}
533
535
  SQL
534
536
 
535
537
  check_info.map do |row|
@@ -537,7 +539,7 @@ module ActiveRecord
537
539
  name: row["conname"],
538
540
  validate: row["valid"]
539
541
  }
540
- expression = row["constraintdef"][/CHECK \({2}(.+)\){2}/, 1]
542
+ expression = row["constraintdef"][/CHECK \((.+)\)/m, 1]
541
543
 
542
544
  CheckConstraintDefinition.new(table_name, expression, options)
543
545
  end
@@ -663,7 +665,12 @@ module ActiveRecord
663
665
  column_name, type, default, notnull, oid, fmod, collation, comment, attgenerated = field
664
666
  type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i)
665
667
  default_value = extract_value_from_default(default)
666
- default_function = extract_default_function(default_value, default)
668
+
669
+ if attgenerated.present?
670
+ default_function = default
671
+ else
672
+ default_function = extract_default_function(default_value, default)
673
+ end
667
674
 
668
675
  if match = default_function&.match(/\Anextval\('"?(?<sequence_name>.+_(?<suffix>seq\d*))"?'::regclass\)\z/)
669
676
  serial = sequence_name_from_parts(table_name, column_name, match[:suffix]) == match[:sequence_name]