activerecord 8.0.3 → 8.1.0.rc1
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 +520 -514
- 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 +2 -0
- 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 +2 -0
- 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/time_zone_conversion.rb +10 -2
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +0 -2
- 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 +16 -3
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -12
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +405 -72
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +55 -40
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +19 -3
- 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 +85 -22
- data/lib/active_record/connection_adapters/abstract/transaction.rb +25 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +86 -20
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +43 -13
- 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 -2
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +17 -15
- 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 +8 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +8 -21
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +66 -31
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +81 -48
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +23 -7
- data/lib/active_record/connection_adapters/schema_cache.rb +2 -2
- 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_statements.rb +4 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -30
- 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 +2 -1
- 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 +53 -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/encryptor.rb +12 -0
- data/lib/active_record/encryption/scheme.rb +1 -1
- data/lib/active_record/enum.rb +24 -8
- data/lib/active_record/errors.rb +20 -4
- data/lib/active_record/explain.rb +1 -1
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixtures.rb +2 -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 +12 -7
- 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 +2 -6
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +14 -1
- 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 +36 -10
- 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/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +15 -3
- 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 +25 -11
- 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 +27 -11
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +7 -7
- data/lib/active_record/relation/predicate_builder.rb +9 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +38 -28
- data/lib/active_record/relation/where_clause.rb +1 -8
- data/lib/active_record/relation.rb +24 -12
- data/lib/active_record/result.rb +44 -21
- data/lib/active_record/runtime_registry.rb +41 -58
- 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/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +5 -20
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +25 -34
- 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 +14 -4
- 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 -4
- 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/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
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- metadata +14 -10
- data/lib/active_record/explain_subscriber.rb +0 -34
- 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",
|
|
@@ -199,8 +209,6 @@ module ActiveRecord
|
|
|
199
209
|
}
|
|
200
210
|
end
|
|
201
211
|
|
|
202
|
-
# HELPER METHODS ===========================================
|
|
203
|
-
|
|
204
212
|
# Must return the MySQL error number from the exception, if the exception has an
|
|
205
213
|
# error number.
|
|
206
214
|
def error_number(exception) # :nodoc:
|
|
@@ -457,6 +465,24 @@ module ActiveRecord
|
|
|
457
465
|
CreateIndexDefinition.new(index, algorithm)
|
|
458
466
|
end
|
|
459
467
|
|
|
468
|
+
def enable_index(table_name, index_name) # :nodoc:
|
|
469
|
+
raise NotImplementedError unless supports_disabling_indexes?
|
|
470
|
+
|
|
471
|
+
query = <<~SQL
|
|
472
|
+
ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "NOT IGNORED" : "VISIBLE"}
|
|
473
|
+
SQL
|
|
474
|
+
execute(query)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
def disable_index(table_name, index_name) # :nodoc:
|
|
478
|
+
raise NotImplementedError unless supports_disabling_indexes?
|
|
479
|
+
|
|
480
|
+
query = <<~SQL
|
|
481
|
+
ALTER TABLE #{quote_table_name(table_name)} ALTER INDEX #{index_name} #{mariadb? ? "IGNORED" : "INVISIBLE"}
|
|
482
|
+
SQL
|
|
483
|
+
execute(query)
|
|
484
|
+
end
|
|
485
|
+
|
|
460
486
|
def add_sql_comment!(sql, comment) # :nodoc:
|
|
461
487
|
sql << " COMMENT #{quote(comment)}" if comment.present?
|
|
462
488
|
sql
|
|
@@ -732,12 +758,12 @@ module ActiveRecord
|
|
|
732
758
|
m.alias_type %r(bit)i, "binary"
|
|
733
759
|
end
|
|
734
760
|
|
|
735
|
-
def register_integer_type(mapping, key,
|
|
761
|
+
def register_integer_type(mapping, key, limit:)
|
|
736
762
|
mapping.register_type(key) do |sql_type|
|
|
737
763
|
if /\bunsigned\b/.match?(sql_type)
|
|
738
|
-
Type::UnsignedInteger.new(
|
|
764
|
+
Type::UnsignedInteger.new(limit: limit)
|
|
739
765
|
else
|
|
740
|
-
Type::Integer.new(
|
|
766
|
+
Type::Integer.new(limit: limit)
|
|
741
767
|
end
|
|
742
768
|
end
|
|
743
769
|
end
|
|
@@ -767,13 +793,13 @@ module ActiveRecord
|
|
|
767
793
|
end
|
|
768
794
|
end
|
|
769
795
|
|
|
770
|
-
def handle_warnings(sql)
|
|
796
|
+
def handle_warnings(_initial_result, sql)
|
|
771
797
|
return if ActiveRecord.db_warnings_action.nil? || @raw_connection.warning_count == 0
|
|
772
798
|
|
|
773
799
|
warning_count = @raw_connection.warning_count
|
|
774
800
|
result = @raw_connection.query("SHOW WARNINGS")
|
|
775
801
|
result = [
|
|
776
|
-
["Warning", nil, "Query had warning_count=#{warning_count} but
|
|
802
|
+
["Warning", nil, "Query had warning_count=#{warning_count} but `SHOW WARNINGS` did not return the warnings. Check MySQL logs or database configuration."],
|
|
777
803
|
] if result.count == 0
|
|
778
804
|
result.each do |level, code, message|
|
|
779
805
|
warning = SQLWarning.new(message, code, level, sql, @pool)
|
|
@@ -810,6 +836,8 @@ module ActiveRecord
|
|
|
810
836
|
CR_SERVER_LOST = 2013
|
|
811
837
|
ER_QUERY_TIMEOUT = 3024
|
|
812
838
|
ER_FK_INCOMPATIBLE_COLUMNS = 3780
|
|
839
|
+
ER_CHECK_CONSTRAINT_VIOLATED = 3819
|
|
840
|
+
ER_CONSTRAINT_FAILED = 4025
|
|
813
841
|
ER_CLIENT_INTERACTION_TIMEOUT = 4031
|
|
814
842
|
|
|
815
843
|
def translate_exception(exception, message:, sql:, binds:)
|
|
@@ -842,6 +870,8 @@ module ActiveRecord
|
|
|
842
870
|
RangeError.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
843
871
|
when ER_NOT_NULL_VIOLATION, ER_DO_NOT_HAVE_DEFAULT
|
|
844
872
|
NotNullViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
873
|
+
when ER_CHECK_CONSTRAINT_VIOLATED, ER_CONSTRAINT_FAILED
|
|
874
|
+
CheckViolation.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
845
875
|
when ER_LOCK_DEADLOCK
|
|
846
876
|
Deadlocked.new(message, sql: sql, binds: binds, connection_pool: @pool)
|
|
847
877
|
when ER_LOCK_WAIT_TIMEOUT
|
|
@@ -960,7 +990,7 @@ module ActiveRecord
|
|
|
960
990
|
end
|
|
961
991
|
|
|
962
992
|
def column_definitions(table_name) # :nodoc:
|
|
963
|
-
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA")
|
|
993
|
+
internal_exec_query("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", "SCHEMA", allow_retry: true)
|
|
964
994
|
end
|
|
965
995
|
|
|
966
996
|
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 # :nodoc:
|
|
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
|
|
@@ -13,6 +13,7 @@ module ActiveRecord
|
|
|
13
13
|
ER_BAD_DB_ERROR = 1049
|
|
14
14
|
ER_DBACCESS_DENIED_ERROR = 1044
|
|
15
15
|
ER_ACCESS_DENIED_ERROR = 1045
|
|
16
|
+
ER_UNKNOWN_STMT_HANDLER = 1243
|
|
16
17
|
ER_CONN_HOST_ERROR = 2003
|
|
17
18
|
ER_UNKNOWN_HOST_ERROR = 2005
|
|
18
19
|
|
|
@@ -91,8 +92,6 @@ module ActiveRecord
|
|
|
91
92
|
true
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
# HELPER METHODS ===========================================
|
|
95
|
-
|
|
96
95
|
def error_number(exception)
|
|
97
96
|
exception.error_number if exception.respond_to?(:error_number)
|
|
98
97
|
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
|
|
|
@@ -170,25 +170,27 @@ module ActiveRecord
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
verified!
|
|
173
|
-
|
|
173
|
+
|
|
174
|
+
notification_payload[:affected_rows] = result.cmd_tuples
|
|
174
175
|
notification_payload[:row_count] = result.ntuples
|
|
175
176
|
result
|
|
176
177
|
end
|
|
177
178
|
|
|
178
179
|
def cast_result(result)
|
|
179
|
-
if result.fields.empty?
|
|
180
|
-
result.
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
ar_result = if result.fields.empty?
|
|
181
|
+
ActiveRecord::Result.empty(affected_rows: result.cmd_tuples)
|
|
182
|
+
else
|
|
183
|
+
fields = result.fields
|
|
184
|
+
types = Array.new(fields.size)
|
|
185
|
+
fields.size.times do |index|
|
|
186
|
+
ftype = result.ftype(index)
|
|
187
|
+
fmod = result.fmod(index)
|
|
188
|
+
types[index] = get_oid_type(ftype, fmod, fields[index])
|
|
189
|
+
end
|
|
183
190
|
|
|
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)
|
|
191
|
+
ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
|
|
190
192
|
end
|
|
191
|
-
|
|
193
|
+
|
|
192
194
|
result.clear
|
|
193
195
|
ar_result
|
|
194
196
|
end
|
|
@@ -220,7 +222,7 @@ module ActiveRecord
|
|
|
220
222
|
pk unless pk.is_a?(Array)
|
|
221
223
|
end
|
|
222
224
|
|
|
223
|
-
def handle_warnings(sql)
|
|
225
|
+
def handle_warnings(result, sql)
|
|
224
226
|
@notice_receiver_sql_warnings.each do |warning|
|
|
225
227
|
next if warning_ignored?(warning)
|
|
226
228
|
|
|
@@ -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
|