activerecord 8.0.5.1 → 8.1.0.beta1
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 +421 -657
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/belongs_to_association.rb +0 -2
- data/lib/active_record/associations/builder/association.rb +16 -5
- data/lib/active_record/associations/builder/belongs_to.rb +17 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_one.rb +1 -1
- data/lib/active_record/associations/builder/singular_association.rb +33 -5
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/preloader/batch.rb +1 -7
- data/lib/active_record/associations/preloader/branch.rb +1 -0
- data/lib/active_record/associations.rb +159 -21
- data/lib/active_record/attribute_methods/serialization.rb +16 -3
- data/lib/active_record/attribute_methods.rb +1 -1
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +2 -2
- data/lib/active_record/base.rb +2 -3
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +1 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +15 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +383 -52
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +27 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +25 -18
- data/lib/active_record/connection_adapters/abstract/quoting.rb +15 -24
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +26 -34
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +88 -25
- data/lib/active_record/connection_adapters/abstract/transaction.rb +16 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +96 -49
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -11
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +4 -4
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +42 -5
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +26 -4
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +27 -22
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -0
- data/lib/active_record/connection_adapters/postgresql/column.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +18 -23
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +21 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +65 -30
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +74 -38
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +10 -12
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- data/lib/active_record/connection_adapters/sqlite3/column.rb +2 -8
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +37 -25
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +3 -15
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -33
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +4 -3
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -1
- data/lib/active_record/connection_adapters.rb +1 -0
- data/lib/active_record/connection_handling.rb +8 -12
- data/lib/active_record/core.rb +5 -4
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +5 -1
- data/lib/active_record/database_configurations/hash_config.rb +50 -9
- data/lib/active_record/database_configurations/url_config.rb +13 -3
- data/lib/active_record/database_configurations.rb +7 -3
- data/lib/active_record/delegated_type.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +54 -69
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +1 -1
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +23 -7
- data/lib/active_record/explain_registry.rb +0 -1
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/future_result.rb +0 -2
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +7 -0
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +1 -5
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +15 -2
- data/lib/active_record/migration/compatibility.rb +34 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +26 -16
- data/lib/active_record/model_schema.rb +10 -7
- data/lib/active_record/nested_attributes.rb +2 -0
- data/lib/active_record/persistence.rb +34 -3
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +3 -7
- data/lib/active_record/railtie.rb +32 -3
- data/lib/active_record/railties/databases.rake +16 -4
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +42 -3
- data/lib/active_record/relation/batches.rb +26 -12
- data/lib/active_record/relation/calculations.rb +20 -9
- data/lib/active_record/relation/delegation.rb +0 -1
- data/lib/active_record/relation/finder_methods.rb +28 -12
- data/lib/active_record/relation/merger.rb +2 -2
- data/lib/active_record/relation/predicate_builder/array_handler.rb +1 -3
- data/lib/active_record/relation/predicate_builder.rb +2 -2
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +40 -31
- data/lib/active_record/relation/where_clause.rb +2 -11
- data/lib/active_record/relation.rb +26 -14
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/sanitization.rb +2 -0
- data/lib/active_record/schema_dumper.rb +12 -10
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +13 -9
- data/lib/active_record/store.rb +44 -19
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +2 -21
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -40
- data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -39
- data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -26
- data/lib/active_record/test_databases.rb +10 -2
- data/lib/active_record/test_fixtures.rb +27 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +32 -10
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +11 -9
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +2 -1
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record.rb +65 -3
- data/lib/arel/alias_predication.rb +2 -0
- data/lib/arel/crud.rb +6 -11
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/visitors/dot.rb +0 -3
- data/lib/arel/visitors/postgresql.rb +55 -0
- data/lib/arel/visitors/sqlite.rb +55 -8
- data/lib/arel/visitors/to_sql.rb +3 -21
- data/lib/arel.rb +3 -1
- metadata +14 -10
- data/lib/active_record/normalization.rb +0 -163
|
@@ -42,7 +42,7 @@ module ActiveRecord
|
|
|
42
42
|
date: { name: "date" },
|
|
43
43
|
binary: { name: "blob" },
|
|
44
44
|
blob: { name: "blob" },
|
|
45
|
-
boolean: { name: "
|
|
45
|
+
boolean: { name: "boolean" },
|
|
46
46
|
json: { name: "json" },
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -81,6 +81,10 @@ module ActiveRecord
|
|
|
81
81
|
|
|
82
82
|
find_cmd_and_exec(ActiveRecord.database_cli[:mysql], *args)
|
|
83
83
|
end
|
|
84
|
+
|
|
85
|
+
def native_database_types # :nodoc:
|
|
86
|
+
NATIVE_DATABASE_TYPES
|
|
87
|
+
end
|
|
84
88
|
end
|
|
85
89
|
|
|
86
90
|
def get_database_version # :nodoc:
|
|
@@ -178,6 +182,16 @@ module ActiveRecord
|
|
|
178
182
|
supports_insert_returning? ? column.auto_populated? : column.auto_increment?
|
|
179
183
|
end
|
|
180
184
|
|
|
185
|
+
# See https://dev.mysql.com/doc/refman/8.0/en/invisible-indexes.html for more details on MySQL feature.
|
|
186
|
+
# See https://mariadb.com/kb/en/ignored-indexes/ for more details on the MariaDB feature.
|
|
187
|
+
def supports_disabling_indexes?
|
|
188
|
+
if mariadb?
|
|
189
|
+
database_version >= "10.6.0"
|
|
190
|
+
else
|
|
191
|
+
database_version >= "8.0.0"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
181
195
|
def get_advisory_lock(lock_name, timeout = 0) # :nodoc:
|
|
182
196
|
query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1
|
|
183
197
|
end
|
|
@@ -186,10 +200,6 @@ module ActiveRecord
|
|
|
186
200
|
query_value("SELECT RELEASE_LOCK(#{quote(lock_name.to_s)})") == 1
|
|
187
201
|
end
|
|
188
202
|
|
|
189
|
-
def native_database_types
|
|
190
|
-
NATIVE_DATABASE_TYPES
|
|
191
|
-
end
|
|
192
|
-
|
|
193
203
|
def index_algorithms
|
|
194
204
|
{
|
|
195
205
|
default: "ALGORITHM = DEFAULT",
|
|
@@ -457,6 +467,24 @@ module ActiveRecord
|
|
|
457
467
|
CreateIndexDefinition.new(index, algorithm)
|
|
458
468
|
end
|
|
459
469
|
|
|
470
|
+
def enable_index(table_name, index_name) # :nodoc:
|
|
471
|
+
raise NotImplementedError unless supports_disabling_indexes?
|
|
472
|
+
|
|
473
|
+
query = <<~SQL
|
|
474
|
+
ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
|
|
475
|
+
SQL
|
|
476
|
+
execute(query)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def disable_index(table_name, index_name) # :nodoc:
|
|
480
|
+
raise NotImplementedError unless supports_disabling_indexes?
|
|
481
|
+
|
|
482
|
+
query = <<~SQL
|
|
483
|
+
ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "IGNORED" : "INVISIBLE"}
|
|
484
|
+
SQL
|
|
485
|
+
execute(query)
|
|
486
|
+
end
|
|
487
|
+
|
|
460
488
|
def add_sql_comment!(sql, comment) # :nodoc:
|
|
461
489
|
sql << " COMMENT #{quote(comment)}" if comment.present?
|
|
462
490
|
sql
|
|
@@ -732,12 +760,12 @@ module ActiveRecord
|
|
|
732
760
|
m.alias_type %r(bit)i, "binary"
|
|
733
761
|
end
|
|
734
762
|
|
|
735
|
-
def register_integer_type(mapping, key,
|
|
763
|
+
def register_integer_type(mapping, key, limit:)
|
|
736
764
|
mapping.register_type(key) do |sql_type|
|
|
737
765
|
if /\bunsigned\b/.match?(sql_type)
|
|
738
|
-
Type::UnsignedInteger.new(
|
|
766
|
+
Type::UnsignedInteger.new(limit: limit)
|
|
739
767
|
else
|
|
740
|
-
Type::Integer.new(
|
|
768
|
+
Type::Integer.new(limit: limit)
|
|
741
769
|
end
|
|
742
770
|
end
|
|
743
771
|
end
|
|
@@ -767,13 +795,13 @@ module ActiveRecord
|
|
|
767
795
|
end
|
|
768
796
|
end
|
|
769
797
|
|
|
770
|
-
def handle_warnings(sql)
|
|
798
|
+
def handle_warnings(_initial_result, sql)
|
|
771
799
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
|
772
800
|
|
|
773
801
|
warning_count = @raw_connection.warning_count
|
|
774
802
|
result = @raw_connection.query("SHOW WARNINGS")
|
|
775
803
|
result = [
|
|
776
|
-
["Warning", nil, "Query had warning_count=#{warning_count} but
|
|
804
|
+
["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
|
|
777
805
|
] if result.count == 0
|
|
778
806
|
result.each do |level, code, message|
|
|
779
807
|
warning = SQLWarning.new(message, code, level, sql, @pool)
|
|
@@ -810,6 +838,8 @@ module ActiveRecord
|
|
|
810
838
|
CR_SERVER_LOST = 2013
|
|
811
839
|
ER_QUERY_TIMEOUT = 3024
|
|
812
840
|
ER_FK_INCOMPATIBLE_COLUMNS = 3780
|
|
841
|
+
ER_CHECK_CONSTRAINT_VIOLATED = 3819
|
|
842
|
+
ER_CONSTRAINT_FAILED = 4025
|
|
813
843
|
ER_CLIENT_INTERACTION_TIMEOUT = 4031
|
|
814
844
|
|
|
815
845
|
def translate_exception(exception, message:, sql:, binds:)
|
|
@@ -842,6 +872,8 @@ module ActiveRecord
|
|
|
842
872
|
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
843
873
|
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
|
844
874
|
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
875
|
+
when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
|
|
876
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
845
877
|
when ER_LOCK_DEADLOCK
|
|
846
878
|
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
847
879
|
when ER_LOCK_WAIT_TIMEOUT
|
|
@@ -960,7 +992,7 @@ module ActiveRecord
|
|
|
960
992
|
end
|
|
961
993
|
|
|
962
994
|
def column_definitions(table_name) # :nodoc:
|
|
963
|
-
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
|
|
995
|
+
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
|
|
964
996
|
end
|
|
965
997
|
|
|
966
998
|
def create_table_info(table_name) # :nodoc:
|
|
@@ -17,16 +17,22 @@ module ActiveRecord
|
|
|
17
17
|
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
|
|
18
18
|
# +sql_type_metadata+ is various information about the type of the column
|
|
19
19
|
# +null+ determines if this column allows +NULL+ values.
|
|
20
|
-
def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
|
|
20
|
+
def initialize(name, cast_type, default, sql_type_metadata = nil, null = true, default_function = nil, collation: nil, comment: nil, **)
|
|
21
21
|
@name = name.freeze
|
|
22
|
+
@cast_type = cast_type
|
|
22
23
|
@sql_type_metadata = sql_type_metadata
|
|
23
24
|
@null = null
|
|
24
|
-
@default = default
|
|
25
|
+
@default = default.nil? || cast_type.mutable? ? default : cast_type.deserialize(default)
|
|
25
26
|
@default_function = default_function
|
|
26
27
|
@collation = collation
|
|
27
28
|
@comment = comment
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
def fetch_cast_type(connection) # :nodoc:
|
|
32
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
33
|
+
@cast_type || connection.lookup_cast_type(sql_type)
|
|
34
|
+
end
|
|
35
|
+
|
|
30
36
|
def has_default?
|
|
31
37
|
!default.nil? || default_function
|
|
32
38
|
end
|
|
@@ -45,6 +51,7 @@ module ActiveRecord
|
|
|
45
51
|
|
|
46
52
|
def init_with(coder)
|
|
47
53
|
@name = coder["name"]
|
|
54
|
+
@cast_type = coder["cast_type"]
|
|
48
55
|
@sql_type_metadata = coder["sql_type_metadata"]
|
|
49
56
|
@null = coder["null"]
|
|
50
57
|
@default = coder["default"]
|
|
@@ -55,6 +62,7 @@ module ActiveRecord
|
|
|
55
62
|
|
|
56
63
|
def encode_with(coder)
|
|
57
64
|
coder["name"] = @name
|
|
65
|
+
coder["cast_type"] = @cast_type
|
|
58
66
|
coder["sql_type_metadata"] = @sql_type_metadata
|
|
59
67
|
coder["null"] = @null
|
|
60
68
|
coder["default"] = @default
|
|
@@ -75,6 +83,7 @@ module ActiveRecord
|
|
|
75
83
|
def ==(other)
|
|
76
84
|
other.is_a?(Column) &&
|
|
77
85
|
name == other.name &&
|
|
86
|
+
cast_type == other.cast_type &&
|
|
78
87
|
default == other.default &&
|
|
79
88
|
sql_type_metadata == other.sql_type_metadata &&
|
|
80
89
|
null == other.null &&
|
|
@@ -88,6 +97,7 @@ module ActiveRecord
|
|
|
88
97
|
Column.hash ^
|
|
89
98
|
name.hash ^
|
|
90
99
|
name.encoding.hash ^
|
|
100
|
+
cast_type.hash ^
|
|
91
101
|
default.hash ^
|
|
92
102
|
sql_type_metadata.hash ^
|
|
93
103
|
null.hash ^
|
|
@@ -100,11 +110,14 @@ module ActiveRecord
|
|
|
100
110
|
false
|
|
101
111
|
end
|
|
102
112
|
|
|
113
|
+
protected
|
|
114
|
+
attr_reader :cast_type
|
|
115
|
+
|
|
103
116
|
private
|
|
104
117
|
def deduplicated
|
|
105
118
|
@name = -name
|
|
106
119
|
@sql_type_metadata = sql_type_metadata.deduplicate if sql_type_metadata
|
|
107
|
-
@default = -default if default
|
|
120
|
+
@default = -default if String === default
|
|
108
121
|
@default_function = -default_function if default_function
|
|
109
122
|
@collation = -collation if collation
|
|
110
123
|
@comment = -comment if comment
|
|
@@ -114,7 +127,7 @@ module ActiveRecord
|
|
|
114
127
|
|
|
115
128
|
class NullColumn < Column
|
|
116
129
|
def initialize(name, **)
|
|
117
|
-
super(name, nil)
|
|
130
|
+
super(name, nil, nil)
|
|
118
131
|
end
|
|
119
132
|
end
|
|
120
133
|
end
|
|
@@ -45,16 +45,16 @@ module ActiveRecord
|
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
+
def default_insert_value(column) # :nodoc:
|
|
49
|
+
super unless column.auto_increment?
|
|
50
|
+
end
|
|
51
|
+
|
|
48
52
|
private
|
|
49
53
|
# https://mariadb.com/kb/en/analyze-statement/
|
|
50
54
|
def analyze_without_explain?
|
|
51
55
|
mariadb? && database_version >= "10.1.0"
|
|
52
56
|
end
|
|
53
57
|
|
|
54
|
-
def default_insert_value(column)
|
|
55
|
-
super unless column.auto_increment?
|
|
56
|
-
end
|
|
57
|
-
|
|
58
58
|
def returning_column_values(result)
|
|
59
59
|
if supports_insert_returning?
|
|
60
60
|
result.rows.first
|
|
@@ -49,6 +49,8 @@ module ActiveRecord
|
|
|
49
49
|
sql << "USING #{o.using}" if o.using
|
|
50
50
|
sql << "ON #{quote_table_name(o.table)}" if create
|
|
51
51
|
sql << "(#{quoted_columns(o)})"
|
|
52
|
+
sql << "INVISIBLE" if o.disabled? && !mariadb?
|
|
53
|
+
sql << "IGNORED" if o.disabled? && mariadb?
|
|
52
54
|
|
|
53
55
|
add_sql_comment!(sql.join(" "), o.comment)
|
|
54
56
|
end
|
|
@@ -5,6 +5,7 @@ module ActiveRecord
|
|
|
5
5
|
module MySQL
|
|
6
6
|
module ColumnMethods
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
|
+
extend ConnectionAdapters::ColumnMethods::ClassMethods
|
|
8
9
|
|
|
9
10
|
##
|
|
10
11
|
# :method: blob
|
|
@@ -42,12 +43,26 @@ module ActiveRecord
|
|
|
42
43
|
# :method: unsigned_bigint
|
|
43
44
|
# :call-seq: unsigned_bigint(*names, **options)
|
|
44
45
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
define_column_methods :blob, :tinyblob, :mediumblob, :longblob,
|
|
47
|
+
:tinytext, :mediumtext, :longtext, :unsigned_integer, :unsigned_bigint
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# = Active Record MySQL Adapter \Index Definition
|
|
51
|
+
class IndexDefinition < ActiveRecord::ConnectionAdapters::IndexDefinition
|
|
52
|
+
attr_accessor :enabled
|
|
53
|
+
|
|
54
|
+
def initialize(*args, **kwargs)
|
|
55
|
+
@enabled = kwargs.key?(:enabled) ? kwargs.delete(:enabled) : true
|
|
56
|
+
super
|
|
57
|
+
end
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
def defined_for?(columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, enabled: nil, **options)
|
|
60
|
+
super(columns, name:, unique:, valid:, include:, nulls_not_distinct:, **options) &&
|
|
61
|
+
(enabled.nil? || self.enabled == enabled)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def disabled?
|
|
65
|
+
!@enabled
|
|
51
66
|
end
|
|
52
67
|
end
|
|
53
68
|
|
|
@@ -100,6 +115,28 @@ module ActiveRecord
|
|
|
100
115
|
# = Active Record MySQL Adapter \Table
|
|
101
116
|
class Table < ActiveRecord::ConnectionAdapters::Table
|
|
102
117
|
include ColumnMethods
|
|
118
|
+
|
|
119
|
+
# Enables an index to be used by query optimizers.
|
|
120
|
+
#
|
|
121
|
+
# t.enable_index(:email)
|
|
122
|
+
#
|
|
123
|
+
# Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
|
|
124
|
+
#
|
|
125
|
+
# See {connection.enable_index}[rdoc-ref:SchemaStatements#enable_index]
|
|
126
|
+
def enable_index(index_name)
|
|
127
|
+
@base.enable_index(name, index_name)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Disables an index not to be used by query optimizers.
|
|
131
|
+
#
|
|
132
|
+
# t.disable_index(:email)
|
|
133
|
+
#
|
|
134
|
+
# Note: only supported by MySQL version 8.0.0 and greater, and MariaDB version 10.6.0 and greater.
|
|
135
|
+
#
|
|
136
|
+
# See {connection.disable_index}[rdoc-ref:SchemaStatements#disable_index]
|
|
137
|
+
def disable_index(index_name)
|
|
138
|
+
@base.disable_index(name, index_name)
|
|
139
|
+
end
|
|
103
140
|
end
|
|
104
141
|
end
|
|
105
142
|
end
|
|
@@ -21,7 +21,7 @@ module ActiveRecord
|
|
|
21
21
|
index_using = mysql_index_type
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
index = [
|
|
25
25
|
row["Table"],
|
|
26
26
|
row["Key_name"],
|
|
27
27
|
row["Non_unique"].to_i == 0,
|
|
@@ -30,8 +30,14 @@ module ActiveRecord
|
|
|
30
30
|
orders: {},
|
|
31
31
|
type: index_type,
|
|
32
32
|
using: index_using,
|
|
33
|
-
comment: row["Index_comment"].presence
|
|
33
|
+
comment: row["Index_comment"].presence,
|
|
34
34
|
]
|
|
35
|
+
|
|
36
|
+
if supports_disabling_indexes?
|
|
37
|
+
index[-1][:enabled] = mariadb? ? row["Ignored"] == "NO" : row["Visible"] == "YES"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
indexes << index
|
|
35
41
|
end
|
|
36
42
|
|
|
37
43
|
if expression = row["Expression"]
|
|
@@ -63,8 +69,7 @@ module ActiveRecord
|
|
|
63
69
|
columns, order: orders, length: lengths
|
|
64
70
|
).values.join(", ")
|
|
65
71
|
end
|
|
66
|
-
|
|
67
|
-
IndexDefinition.new(*index, **options)
|
|
72
|
+
MySQL::IndexDefinition.new(*index, **options)
|
|
68
73
|
end
|
|
69
74
|
rescue StatementInvalid => e
|
|
70
75
|
if e.message.match?(/Table '.+' doesn't exist/)
|
|
@@ -74,6 +79,16 @@ module ActiveRecord
|
|
|
74
79
|
end
|
|
75
80
|
end
|
|
76
81
|
|
|
82
|
+
def create_index_definition(table_name, name, unique, columns, **options)
|
|
83
|
+
MySQL::IndexDefinition.new(table_name, name, unique, columns, **options)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def add_index_options(table_name, column_name, name: nil, if_not_exists: false, internal: false, **options) # :nodoc:
|
|
87
|
+
index, algorithm, if_not_exists = super
|
|
88
|
+
index.enabled = options[:enabled] unless options[:enabled].nil?
|
|
89
|
+
[index, algorithm, if_not_exists]
|
|
90
|
+
end
|
|
91
|
+
|
|
77
92
|
def remove_column(table_name, column_name, type = nil, **options)
|
|
78
93
|
if foreign_key_exists?(table_name, column: column_name)
|
|
79
94
|
remove_foreign_key(table_name, column: column_name)
|
|
@@ -209,6 +224,7 @@ module ActiveRecord
|
|
|
209
224
|
|
|
210
225
|
MySQL::Column.new(
|
|
211
226
|
field["Field"],
|
|
227
|
+
lookup_cast_type(type_metadata.sql_type),
|
|
212
228
|
default,
|
|
213
229
|
type_metadata,
|
|
214
230
|
field["Null"] == "YES",
|
|
@@ -233,6 +249,12 @@ module ActiveRecord
|
|
|
233
249
|
end
|
|
234
250
|
end
|
|
235
251
|
|
|
252
|
+
def valid_index_options
|
|
253
|
+
index_options = super
|
|
254
|
+
index_options << :enabled if supports_disabling_indexes?
|
|
255
|
+
index_options
|
|
256
|
+
end
|
|
257
|
+
|
|
236
258
|
def add_options_for_index_columns(quoted_columns, **options)
|
|
237
259
|
quoted_columns = add_index_length(quoted_columns, **options)
|
|
238
260
|
super
|
|
@@ -50,34 +50,40 @@ module ActiveRecord
|
|
|
50
50
|
|
|
51
51
|
result = nil
|
|
52
52
|
if binds.nil? || binds.empty?
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@affected_rows_before_warnings = result&.size || raw_connection.affected_rows
|
|
61
|
-
end
|
|
53
|
+
result = raw_connection.query(sql)
|
|
54
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
55
|
+
# As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
|
|
56
|
+
# from that same connection was GCed while `#query` released the GVL.
|
|
57
|
+
# By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
|
|
58
|
+
# of hitting the bug.
|
|
59
|
+
@affected_rows_before_warnings = result&.size || raw_connection.affected_rows
|
|
62
60
|
elsif prepare
|
|
63
|
-
|
|
61
|
+
retry_count = 1
|
|
64
62
|
begin
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
rescue ::Mysql2::Error
|
|
63
|
+
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
|
64
|
+
result = stmt.execute(*type_casted_binds)
|
|
65
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
66
|
+
rescue ::Mysql2::Error => error
|
|
70
67
|
@statements.delete(sql)
|
|
68
|
+
# Sometimes for an unknown reason, we get that error.
|
|
69
|
+
# It suggest somehow that the prepared statement was deallocated
|
|
70
|
+
# but the client doesn't know it.
|
|
71
|
+
# But we know that this error is safe to retry, so we do so after
|
|
72
|
+
# getting rid of the originally cached statement.
|
|
73
|
+
if error.error_number == Mysql2Adapter::ER_UNKNOWN_STMT_HANDLER
|
|
74
|
+
if retry_count.positive?
|
|
75
|
+
retry_count -= 1
|
|
76
|
+
retry
|
|
77
|
+
end
|
|
78
|
+
end
|
|
71
79
|
raise
|
|
72
80
|
end
|
|
73
81
|
else
|
|
74
82
|
stmt = raw_connection.prepare(sql)
|
|
75
83
|
|
|
76
84
|
begin
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
@affected_rows_before_warnings = stmt.affected_rows
|
|
80
|
-
end
|
|
85
|
+
result = stmt.execute(*type_casted_binds)
|
|
86
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
81
87
|
|
|
82
88
|
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
83
89
|
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
|
@@ -100,7 +106,6 @@ module ActiveRecord
|
|
|
100
106
|
raw_connection.abandon_results!
|
|
101
107
|
|
|
102
108
|
verified!
|
|
103
|
-
handle_warnings(sql)
|
|
104
109
|
result
|
|
105
110
|
ensure
|
|
106
111
|
if reset_multi_statement && active?
|
|
@@ -109,12 +114,12 @@ module ActiveRecord
|
|
|
109
114
|
end
|
|
110
115
|
|
|
111
116
|
def cast_result(raw_result)
|
|
112
|
-
return ActiveRecord::Result.empty if raw_result.nil?
|
|
117
|
+
return ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings) if raw_result.nil?
|
|
113
118
|
|
|
114
119
|
fields = raw_result.fields
|
|
115
120
|
|
|
116
121
|
result = if fields.empty?
|
|
117
|
-
ActiveRecord::Result.empty
|
|
122
|
+
ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings)
|
|
118
123
|
else
|
|
119
124
|
ActiveRecord::Result.new(fields, raw_result.to_a)
|
|
120
125
|
end
|
|
@@ -65,8 +65,7 @@ module ActiveRecord
|
|
|
65
65
|
other.is_a?(Column) &&
|
|
66
66
|
super &&
|
|
67
67
|
identity? == other.identity? &&
|
|
68
|
-
serial? == other.serial?
|
|
69
|
-
virtual? == other.virtual?
|
|
68
|
+
serial? == other.serial?
|
|
70
69
|
end
|
|
71
70
|
alias :eql? :==
|
|
72
71
|
|
|
@@ -74,8 +73,7 @@ module ActiveRecord
|
|
|
74
73
|
Column.hash ^
|
|
75
74
|
super.hash ^
|
|
76
75
|
identity?.hash ^
|
|
77
|
-
serial?.hash
|
|
78
|
-
virtual?.hash
|
|
76
|
+
serial?.hash
|
|
79
77
|
end
|
|
80
78
|
end
|
|
81
79
|
end
|
|
@@ -11,8 +11,8 @@ module ActiveRecord
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
# Queries the database and returns the results in an Array-like object
|
|
14
|
-
def query(sql, name = nil) # :nodoc:
|
|
15
|
-
result = internal_execute(sql, name)
|
|
14
|
+
def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
|
|
15
|
+
result = internal_execute(sql, name, allow_retry:, materialize_transactions:)
|
|
16
16
|
result.map_types!(@type_map_for_results).values
|
|
17
17
|
end
|
|
18
18
|
|
|
@@ -127,14 +127,7 @@ module ActiveRecord
|
|
|
127
127
|
def cancel_any_running_query
|
|
128
128
|
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
# because the pg gem cannot obtain the backend_key in that case.
|
|
132
|
-
# This method is only called from exec_rollback_db_transaction and exec_restart_db_transaction.
|
|
133
|
-
# Even without cancel, rollback will still run. However, since any running
|
|
134
|
-
# query must finish first, the rollback may take longer.
|
|
135
|
-
if !(PG.library_version >= 18_00_00 && Gem::Version.new(PG::VERSION) < Gem::Version.new("1.6.0"))
|
|
136
|
-
@raw_connection.cancel
|
|
137
|
-
end
|
|
130
|
+
@raw_connection.cancel
|
|
138
131
|
@raw_connection.block
|
|
139
132
|
rescue PG::Error
|
|
140
133
|
end
|
|
@@ -170,25 +163,27 @@ module ActiveRecord
|
|
|
170
163
|
end
|
|
171
164
|
|
|
172
165
|
verified!
|
|
173
|
-
|
|
166
|
+
|
|
167
|
+
notification_payload[:affected_rows] = result.cmd_tuples
|
|
174
168
|
notification_payload[:row_count] = result.ntuples
|
|
175
169
|
result
|
|
176
170
|
end
|
|
177
171
|
|
|
178
172
|
def cast_result(result)
|
|
179
|
-
if result.fields.empty?
|
|
180
|
-
result.
|
|
181
|
-
|
|
182
|
-
|
|
173
|
+
ar_result = if result.fields.empty?
|
|
174
|
+
ActiveRecord::Result.empty(affected_rows: result.cmd_tuples)
|
|
175
|
+
else
|
|
176
|
+
fields = result.fields
|
|
177
|
+
types = Array.new(fields.size)
|
|
178
|
+
fields.size.times do |index|
|
|
179
|
+
ftype = result.ftype(index)
|
|
180
|
+
fmod = result.fmod(index)
|
|
181
|
+
types[index] = get_oid_type(ftype, fmod, fields[index])
|
|
182
|
+
end
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
fields = result.fields
|
|
186
|
-
fields.each_with_index do |fname, i|
|
|
187
|
-
ftype = result.ftype i
|
|
188
|
-
fmod = result.fmod i
|
|
189
|
-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
184
|
+
ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
|
|
190
185
|
end
|
|
191
|
-
|
|
186
|
+
|
|
192
187
|
result.clear
|
|
193
188
|
ar_result
|
|
194
189
|
end
|
|
@@ -220,7 +215,7 @@ module ActiveRecord
|
|
|
220
215
|
pk unless pk.is_a?(Array)
|
|
221
216
|
end
|
|
222
217
|
|
|
223
|
-
def handle_warnings(sql)
|
|
218
|
+
def handle_warnings(result, sql)
|
|
224
219
|
@notice_receiver_sql_warnings.each do |warning|
|
|
225
220
|
next if warning_ignored?(warning)
|
|
226
221
|
|
|
@@ -16,8 +16,8 @@ module ActiveRecord
|
|
|
16
16
|
@subtype = subtype
|
|
17
17
|
@delimiter = delimiter
|
|
18
18
|
|
|
19
|
-
@pg_encoder = PG::TextEncoder::Array.new
|
|
20
|
-
@pg_decoder = PG::TextDecoder::Array.new
|
|
19
|
+
@pg_encoder = PG::TextEncoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
20
|
+
@pg_decoder = PG::TextDecoder::Array.new(name: "#{type}[]".freeze, delimiter: delimiter).freeze
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def deserialize(value)
|
|
@@ -153,14 +153,15 @@ module ActiveRecord
|
|
|
153
153
|
"'#{escape_bytea(value.to_s)}'"
|
|
154
154
|
end
|
|
155
155
|
|
|
156
|
+
# `column` may be either an instance of Column or ColumnDefinition.
|
|
156
157
|
def quote_default_expression(value, column) # :nodoc:
|
|
157
158
|
if value.is_a?(Proc)
|
|
158
159
|
value.call
|
|
159
160
|
elsif column.type == :uuid && value.is_a?(String) && value.include?("()")
|
|
160
161
|
value # Does not quote function default values for UUID columns
|
|
161
162
|
elsif column.respond_to?(:array?)
|
|
162
|
-
|
|
163
|
-
quote(
|
|
163
|
+
# TODO: Remove fetch_cast_type and the need for connection after we release 8.1.
|
|
164
|
+
quote(column.fetch_cast_type(self).serialize(value))
|
|
164
165
|
else
|
|
165
166
|
super
|
|
166
167
|
end
|
|
@@ -186,16 +187,12 @@ module ActiveRecord
|
|
|
186
187
|
end
|
|
187
188
|
end
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
# TODO: Make this method private after we release 8.1.
|
|
191
|
+
def lookup_cast_type(sql_type) # :nodoc:
|
|
192
|
+
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
192
193
|
end
|
|
193
194
|
|
|
194
195
|
private
|
|
195
|
-
def lookup_cast_type(sql_type)
|
|
196
|
-
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
|
|
197
|
-
end
|
|
198
|
-
|
|
199
196
|
def encode_array(array_data)
|
|
200
197
|
encoder = array_data.encoder
|
|
201
198
|
values = type_cast_array(array_data.values)
|
|
@@ -208,7 +205,17 @@ module ActiveRecord
|
|
|
208
205
|
end
|
|
209
206
|
|
|
210
207
|
def encode_range(range)
|
|
211
|
-
|
|
208
|
+
lower_bound = type_cast_range_value(range.begin)
|
|
209
|
+
upper_bound = if date_or_time_range?(range)
|
|
210
|
+
# Postgres will convert `[today,]` to `[today,)`, making it exclusive.
|
|
211
|
+
# We can use the special timestamp value `infinity` to force inclusion.
|
|
212
|
+
# https://www.postgresql.org/docs/current/rangetypes.html#RANGETYPES-INFINITE
|
|
213
|
+
range.end.nil? ? "infinity" : type_cast(range.end)
|
|
214
|
+
else
|
|
215
|
+
type_cast_range_value(range.end)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
"[#{lower_bound},#{upper_bound}#{range.exclude_end? ? ')' : ']'}"
|
|
212
219
|
end
|
|
213
220
|
|
|
214
221
|
def determine_encoding_of_strings_in_array(value)
|
|
@@ -232,6 +239,10 @@ module ActiveRecord
|
|
|
232
239
|
def infinity?(value)
|
|
233
240
|
value.respond_to?(:infinite?) && value.infinite?
|
|
234
241
|
end
|
|
242
|
+
|
|
243
|
+
def date_or_time_range?(range)
|
|
244
|
+
[range.begin.class, range.end.class].intersect?([Date, DateTime, Time])
|
|
245
|
+
end
|
|
235
246
|
end
|
|
236
247
|
end
|
|
237
248
|
end
|
|
@@ -99,7 +99,7 @@ module ActiveRecord
|
|
|
99
99
|
if options[:default].nil?
|
|
100
100
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} DROP DEFAULT"
|
|
101
101
|
else
|
|
102
|
-
quoted_default =
|
|
102
|
+
quoted_default = quote_default_expression_for_column_definition(options[:default], column)
|
|
103
103
|
change_column_sql << ", ALTER COLUMN #{quoted_column_name} SET DEFAULT #{quoted_default}"
|
|
104
104
|
end
|
|
105
105
|
end
|