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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +357 -0
- data/MIT-LICENSE +1 -1
- data/lib/active_record/associations/collection_association.rb +1 -2
- data/lib/active_record/associations/collection_proxy.rb +2 -2
- data/lib/active_record/associations/has_many_association.rb +7 -4
- data/lib/active_record/associations/join_dependency.rb +17 -13
- data/lib/active_record/associations.rb +38 -17
- data/lib/active_record/attribute_methods/serialization.rb +34 -50
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods.rb +2 -2
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +3 -3
- data/lib/active_record/coders/yaml_column.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +8 -4
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +5 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +10 -2
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +7 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +20 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +10 -3
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +6 -5
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +14 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +35 -2
- data/lib/active_record/connection_handling.rb +2 -2
- data/lib/active_record/core.rb +3 -3
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations.rb +1 -1
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/encryption/configurable.rb +9 -3
- data/lib/active_record/encryption/contexts.rb +3 -3
- data/lib/active_record/encryption/derived_secret_key_provider.rb +1 -1
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +2 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +2 -2
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +28 -28
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/properties.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +1 -1
- data/lib/active_record/fixtures.rb +5 -5
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/integration.rb +2 -2
- data/lib/active_record/locking/pessimistic.rb +3 -3
- data/lib/active_record/log_subscriber.rb +10 -5
- data/lib/active_record/middleware/database_selector.rb +13 -6
- data/lib/active_record/middleware/shard_selector.rb +4 -4
- data/lib/active_record/migration/command_recorder.rb +3 -3
- data/lib/active_record/migration/compatibility.rb +10 -7
- data/lib/active_record/migration.rb +6 -5
- data/lib/active_record/model_schema.rb +22 -10
- data/lib/active_record/persistence.rb +9 -8
- data/lib/active_record/querying.rb +1 -1
- data/lib/active_record/railtie.rb +22 -18
- data/lib/active_record/railties/databases.rake +16 -11
- data/lib/active_record/reflection.rb +7 -1
- data/lib/active_record/relation/batches.rb +3 -3
- data/lib/active_record/relation/calculations.rb +3 -2
- data/lib/active_record/relation/delegation.rb +1 -1
- data/lib/active_record/relation/query_methods.rb +46 -11
- data/lib/active_record/relation.rb +22 -6
- data/lib/active_record/sanitization.rb +6 -5
- data/lib/active_record/schema.rb +38 -23
- data/lib/active_record/schema_dumper.rb +15 -16
- data/lib/active_record/scoping/default.rb +5 -7
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +2 -2
- data/lib/active_record/store.rb +7 -2
- data/lib/active_record/tasks/database_tasks.rb +32 -23
- data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -2
- data/lib/active_record/test_fixtures.rb +12 -5
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/presence.rb +2 -2
- data/lib/active_record/validations/uniqueness.rb +3 -3
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +15 -1
- 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
|
|
71
|
-
# * +class_name_or_coder+ - Optional
|
|
72
|
-
#
|
|
73
|
-
#
|
|
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
|
-
#
|
|
78
|
-
# is not passed, the previous default value (if any) will
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
# #
|
|
105
|
-
# def self.dump(
|
|
106
|
-
# ActiveSupport::JSON.
|
|
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
|
-
# #
|
|
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.
|
|
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
|
|
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.
|
|
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?) ||
|
|
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
|
|
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)
|
data/lib/active_record/base.rb
CHANGED
|
@@ -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
|
|
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
|
|
155
|
-
# user.admin? # => false, due to the getter
|
|
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
|
-
|
|
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
|
-
|
|
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
|
#
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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) &&
|
|
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
|
|
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
|
|
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)
|
|
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 (
|
|
228
|
-
# non-multi role application,
|
|
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 (
|
|
234
|
-
# a non-sharded application,
|
|
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)
|
|
@@ -53,7 +53,13 @@ module ActiveRecord
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
def schema_precision(column)
|
|
56
|
-
|
|
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.
|
|
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.
|
|
44
|
+
unless value = scanner.scan(/^(\\[\\"]|[^\\"])*?(?=")/)
|
|
45
45
|
raise(ArgumentError, ERROR % scanner.string.inspect)
|
|
46
46
|
end
|
|
47
47
|
|
|
@@ -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 \(
|
|
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
|
-
|
|
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]
|