activerecord 7.2.3.1 → 8.0.5.1
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 +475 -946
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +1 -0
- data/lib/active_record/associations/association.rb +34 -10
- data/lib/active_record/associations/builder/association.rb +7 -6
- data/lib/active_record/associations/collection_association.rb +1 -1
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +2 -2
- data/lib/active_record/associations/preloader/association.rb +2 -2
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +34 -4
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_methods/primary_key.rb +4 -8
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +2 -12
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +34 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +0 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +8 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +91 -44
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +8 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +7 -2
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -10
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +67 -74
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +21 -40
- data/lib/active_record/connection_adapters/mysql/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +50 -45
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +84 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +1 -8
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -43
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +6 -12
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +59 -16
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +53 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +80 -100
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +0 -6
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +27 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +9 -1
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -12
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +37 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +0 -17
- data/lib/active_record/connection_adapters.rb +0 -56
- data/lib/active_record/connection_handling.rb +23 -1
- data/lib/active_record/core.rb +29 -14
- data/lib/active_record/database_configurations/database_config.rb +4 -0
- data/lib/active_record/database_configurations/hash_config.rb +16 -2
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +4 -4
- data/lib/active_record/encryption/encrypted_attribute_type.rb +10 -1
- data/lib/active_record/encryption/encryptor.rb +16 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +8 -1
- data/lib/active_record/enum.rb +9 -22
- data/lib/active_record/errors.rb +13 -5
- data/lib/active_record/fixtures.rb +0 -2
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +3 -3
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +5 -11
- data/lib/active_record/migration/command_recorder.rb +32 -12
- data/lib/active_record/migration/compatibility.rb +5 -2
- data/lib/active_record/migration.rb +38 -42
- data/lib/active_record/model_schema.rb +3 -4
- data/lib/active_record/nested_attributes.rb +4 -6
- data/lib/active_record/persistence.rb +128 -130
- data/lib/active_record/query_logs.rb +102 -50
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +2 -26
- data/lib/active_record/railties/databases.rake +11 -35
- data/lib/active_record/reflection.rb +18 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +132 -72
- data/lib/active_record/relation/calculations.rb +40 -39
- data/lib/active_record/relation/delegation.rb +25 -14
- data/lib/active_record/relation/finder_methods.rb +18 -18
- data/lib/active_record/relation/merger.rb +8 -8
- data/lib/active_record/relation/predicate_builder/array_handler.rb +3 -1
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +13 -0
- data/lib/active_record/relation/query_methods.rb +105 -61
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +1 -1
- data/lib/active_record/relation.rb +80 -62
- data/lib/active_record/result.rb +66 -4
- data/lib/active_record/sanitization.rb +7 -6
- data/lib/active_record/schema_dumper.rb +5 -0
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/statement_cache.rb +14 -14
- data/lib/active_record/store.rb +7 -3
- data/lib/active_record/table_metadata.rb +1 -3
- data/lib/active_record/tasks/database_tasks.rb +69 -60
- data/lib/active_record/tasks/mysql_database_tasks.rb +0 -2
- data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -2
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +12 -0
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transactions.rb +5 -6
- data/lib/active_record/type/serialized.rb +5 -0
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +21 -48
- data/lib/arel/collectors/bind.rb +2 -2
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +2 -2
- data/lib/arel/nodes/binary.rb +1 -1
- data/lib/arel/nodes/node.rb +1 -1
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/table.rb +3 -7
- metadata +10 -11
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -8,45 +8,43 @@ module ActiveRecord
|
|
|
8
8
|
def indexes(table_name)
|
|
9
9
|
indexes = []
|
|
10
10
|
current_index = nil
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
index_using = mysql_index_type
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
indexes << [
|
|
26
|
-
row[:Table],
|
|
27
|
-
row[:Key_name],
|
|
28
|
-
row[:Non_unique].to_i == 0,
|
|
29
|
-
[],
|
|
30
|
-
lengths: {},
|
|
31
|
-
orders: {},
|
|
32
|
-
type: index_type,
|
|
33
|
-
using: index_using,
|
|
34
|
-
comment: row[:Index_comment].presence
|
|
35
|
-
]
|
|
11
|
+
internal_exec_query("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA").each do |row|
|
|
12
|
+
if current_index != row["Key_name"]
|
|
13
|
+
next if row["Key_name"] == "PRIMARY" # skip the primary key
|
|
14
|
+
current_index = row["Key_name"]
|
|
15
|
+
|
|
16
|
+
mysql_index_type = row["Index_type"].downcase.to_sym
|
|
17
|
+
case mysql_index_type
|
|
18
|
+
when :fulltext, :spatial
|
|
19
|
+
index_type = mysql_index_type
|
|
20
|
+
when :btree, :hash
|
|
21
|
+
index_using = mysql_index_type
|
|
36
22
|
end
|
|
37
23
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
24
|
+
indexes << [
|
|
25
|
+
row["Table"],
|
|
26
|
+
row["Key_name"],
|
|
27
|
+
row["Non_unique"].to_i == 0,
|
|
28
|
+
[],
|
|
29
|
+
lengths: {},
|
|
30
|
+
orders: {},
|
|
31
|
+
type: index_type,
|
|
32
|
+
using: index_using,
|
|
33
|
+
comment: row["Index_comment"].presence
|
|
34
|
+
]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if expression = row["Expression"]
|
|
38
|
+
expression = expression.gsub("\\'", "'")
|
|
39
|
+
expression = +"(#{expression})" unless expression.start_with?("(")
|
|
40
|
+
indexes.last[-2] << expression
|
|
41
|
+
indexes.last[-1][:expressions] ||= {}
|
|
42
|
+
indexes.last[-1][:expressions][expression] = expression
|
|
43
|
+
indexes.last[-1][:orders][expression] = :desc if row["Collation"] == "D"
|
|
44
|
+
else
|
|
45
|
+
indexes.last[-2] << row["Column_name"]
|
|
46
|
+
indexes.last[-1][:lengths][row["Column_name"]] = row["Sub_part"].to_i if row["Sub_part"]
|
|
47
|
+
indexes.last[-1][:orders][row["Column_name"]] = :desc if row["Collation"] == "D"
|
|
50
48
|
end
|
|
51
49
|
end
|
|
52
50
|
|
|
@@ -87,6 +85,13 @@ module ActiveRecord
|
|
|
87
85
|
super
|
|
88
86
|
end
|
|
89
87
|
|
|
88
|
+
def remove_foreign_key(from_table, to_table = nil, **options)
|
|
89
|
+
# RESTRICT is by default in MySQL.
|
|
90
|
+
options.delete(:on_update) if options[:on_update] == :restrict
|
|
91
|
+
options.delete(:on_delete) if options[:on_delete] == :restrict
|
|
92
|
+
super
|
|
93
|
+
end
|
|
94
|
+
|
|
90
95
|
def internal_string_options_for_primary_key
|
|
91
96
|
super.tap do |options|
|
|
92
97
|
if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset)
|
|
@@ -182,12 +187,12 @@ module ActiveRecord
|
|
|
182
187
|
end
|
|
183
188
|
|
|
184
189
|
def new_column_from_field(table_name, field, _definitions)
|
|
185
|
-
field_name = field.fetch(
|
|
186
|
-
type_metadata = fetch_type_metadata(field[
|
|
187
|
-
default, default_function = field[
|
|
190
|
+
field_name = field.fetch("Field")
|
|
191
|
+
type_metadata = fetch_type_metadata(field["Type"], field["Extra"])
|
|
192
|
+
default, default_function = field["Default"], nil
|
|
188
193
|
|
|
189
194
|
if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
|
|
190
|
-
default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field[
|
|
195
|
+
default = "#{default} ON UPDATE #{default}" if /on update CURRENT_TIMESTAMP/i.match?(field["Extra"])
|
|
191
196
|
default, default_function = nil, default
|
|
192
197
|
elsif type_metadata.extra == "DEFAULT_GENERATED"
|
|
193
198
|
default = +"(#{default})" unless default.start_with?("(")
|
|
@@ -203,13 +208,13 @@ module ActiveRecord
|
|
|
203
208
|
end
|
|
204
209
|
|
|
205
210
|
MySQL::Column.new(
|
|
206
|
-
field[
|
|
211
|
+
field["Field"],
|
|
207
212
|
default,
|
|
208
213
|
type_metadata,
|
|
209
|
-
field[
|
|
214
|
+
field["Null"] == "YES",
|
|
210
215
|
default_function,
|
|
211
|
-
collation: field[
|
|
212
|
-
comment: field[
|
|
216
|
+
collation: field["Collation"],
|
|
217
|
+
comment: field["Comment"].presence
|
|
213
218
|
)
|
|
214
219
|
end
|
|
215
220
|
|
|
@@ -13,49 +13,10 @@ module ActiveRecord
|
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false) # :nodoc:
|
|
17
|
-
if without_prepared_statement?(binds)
|
|
18
|
-
execute_and_free(sql, name, async: async, allow_retry: allow_retry) do |result|
|
|
19
|
-
if result
|
|
20
|
-
build_result(columns: result.fields, rows: result.to_a)
|
|
21
|
-
else
|
|
22
|
-
build_result(columns: [], rows: [])
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
else
|
|
26
|
-
exec_stmt_and_free(sql, name, binds, cache_stmt: prepare, async: async, allow_retry: allow_retry) do |_, result|
|
|
27
|
-
if result
|
|
28
|
-
build_result(columns: result.fields, rows: result.to_a)
|
|
29
|
-
else
|
|
30
|
-
build_result(columns: [], rows: [])
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
37
|
-
if without_prepared_statement?(binds)
|
|
38
|
-
with_raw_connection do |conn|
|
|
39
|
-
@affected_rows_before_warnings = nil
|
|
40
|
-
execute_and_free(sql, name) { @affected_rows_before_warnings || conn.affected_rows }
|
|
41
|
-
end
|
|
42
|
-
else
|
|
43
|
-
exec_stmt_and_free(sql, name, binds) { |stmt| stmt.affected_rows }
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
alias :exec_update :exec_delete
|
|
47
|
-
|
|
48
16
|
private
|
|
49
|
-
def
|
|
50
|
-
raw_connection.query_options[:database_timezone] = default_timezone
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def execute_batch(statements, name = nil)
|
|
54
|
-
statements = statements.map { |sql| transform_query(sql) }
|
|
17
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
55
18
|
combine_multi_statements(statements).each do |statement|
|
|
56
|
-
|
|
57
|
-
raw_execute(statement, name)
|
|
58
|
-
end
|
|
19
|
+
raw_execute(statement, name, batch: true, **kwargs)
|
|
59
20
|
end
|
|
60
21
|
end
|
|
61
22
|
|
|
@@ -77,73 +38,102 @@ module ActiveRecord
|
|
|
77
38
|
end
|
|
78
39
|
end
|
|
79
40
|
|
|
80
|
-
def
|
|
81
|
-
if multi_statements_enabled?
|
|
82
|
-
|
|
41
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
42
|
+
reset_multi_statement = if batch && !multi_statements_enabled?
|
|
43
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_ON)
|
|
44
|
+
true
|
|
83
45
|
end
|
|
84
46
|
|
|
85
|
-
|
|
86
|
-
|
|
47
|
+
# Make sure we carry over any changes to ActiveRecord.default_timezone that have been
|
|
48
|
+
# made since we established the connection
|
|
49
|
+
raw_connection.query_options[:database_timezone] = default_timezone
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
51
|
+
result = nil
|
|
52
|
+
if binds.nil? || binds.empty?
|
|
53
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
54
|
+
result = raw_connection.query(sql)
|
|
55
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
56
|
+
# As of mysql2 0.5.6 `#affected_rows` might raise Mysql2::Error if a prepared statement
|
|
57
|
+
# from that same connection was GCed while `#query` released the GVL.
|
|
58
|
+
# By avoiding to call `#affected_rows` when we have a result, we reduce the likeliness
|
|
59
|
+
# of hitting the bug.
|
|
60
|
+
@affected_rows_before_warnings = result&.size || raw_connection.affected_rows
|
|
61
|
+
end
|
|
62
|
+
elsif prepare
|
|
63
|
+
stmt = @statements[sql] ||= raw_connection.prepare(sql)
|
|
64
|
+
begin
|
|
65
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
66
|
+
result = stmt.execute(*type_casted_binds)
|
|
67
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
68
|
+
end
|
|
69
|
+
rescue ::Mysql2::Error
|
|
70
|
+
@statements.delete(sql)
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
else
|
|
74
|
+
stmt = raw_connection.prepare(sql)
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
|
78
|
+
result = stmt.execute(*type_casted_binds)
|
|
79
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
80
|
+
end
|
|
93
81
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
result
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
83
|
+
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
|
84
|
+
# that bug happening. It can still happen if `#execute` is used as we have no callback
|
|
85
|
+
# to eagerly close the statement.
|
|
86
|
+
if result
|
|
87
|
+
result.instance_variable_set(:@_ar_stmt_to_close, stmt)
|
|
88
|
+
else
|
|
89
|
+
stmt.close
|
|
90
|
+
end
|
|
91
|
+
rescue ::Mysql2::Error
|
|
92
|
+
stmt.close
|
|
93
|
+
raise
|
|
104
94
|
end
|
|
105
95
|
end
|
|
96
|
+
|
|
97
|
+
notification_payload[:affected_rows] = @affected_rows_before_warnings
|
|
98
|
+
notification_payload[:row_count] = result&.size || 0
|
|
99
|
+
|
|
100
|
+
raw_connection.abandon_results!
|
|
101
|
+
|
|
102
|
+
verified!
|
|
103
|
+
handle_warnings(sql)
|
|
104
|
+
result
|
|
105
|
+
ensure
|
|
106
|
+
if reset_multi_statement && active?
|
|
107
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
|
108
|
+
end
|
|
106
109
|
end
|
|
107
110
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
check_if_write_query(sql)
|
|
111
|
+
def cast_result(raw_result)
|
|
112
|
+
return ActiveRecord::Result.empty if raw_result.nil?
|
|
111
113
|
|
|
112
|
-
|
|
114
|
+
fields = raw_result.fields
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
result = if fields.empty?
|
|
117
|
+
ActiveRecord::Result.empty
|
|
118
|
+
else
|
|
119
|
+
ActiveRecord::Result.new(fields, raw_result.to_a)
|
|
120
|
+
end
|
|
115
121
|
|
|
116
|
-
|
|
117
|
-
with_raw_connection(allow_retry: allow_retry) do |conn|
|
|
118
|
-
sync_timezone_changes(conn)
|
|
122
|
+
free_raw_result(raw_result)
|
|
119
123
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
stmt = conn.prepare(sql)
|
|
124
|
-
end
|
|
124
|
+
result
|
|
125
|
+
end
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
stmt.execute(*type_casted_binds)
|
|
129
|
-
end
|
|
130
|
-
verified!
|
|
131
|
-
result
|
|
132
|
-
rescue ::Mysql2::Error => e
|
|
133
|
-
if cache_stmt
|
|
134
|
-
@statements.delete(sql)
|
|
135
|
-
else
|
|
136
|
-
stmt.close
|
|
137
|
-
end
|
|
138
|
-
raise e
|
|
139
|
-
end
|
|
127
|
+
def affected_rows(raw_result)
|
|
128
|
+
free_raw_result(raw_result) if raw_result
|
|
140
129
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
130
|
+
@affected_rows_before_warnings
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def free_raw_result(raw_result)
|
|
134
|
+
raw_result.free
|
|
135
|
+
if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
|
|
136
|
+
stmt.close
|
|
147
137
|
end
|
|
148
138
|
end
|
|
149
139
|
end
|
|
@@ -55,6 +55,7 @@ module ActiveRecord
|
|
|
55
55
|
def initialize(...)
|
|
56
56
|
super
|
|
57
57
|
|
|
58
|
+
@affected_rows_before_warnings = nil
|
|
58
59
|
@config[:flags] ||= 0
|
|
59
60
|
|
|
60
61
|
if @config[:flags].kind_of? Array
|
|
@@ -92,14 +93,6 @@ module ActiveRecord
|
|
|
92
93
|
|
|
93
94
|
# HELPER METHODS ===========================================
|
|
94
95
|
|
|
95
|
-
def each_hash(result, &block) # :nodoc:
|
|
96
|
-
if block_given?
|
|
97
|
-
result.each(as: :hash, symbolize_keys: true, &block)
|
|
98
|
-
else
|
|
99
|
-
to_enum(:each_hash, result)
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
|
|
103
96
|
def error_number(exception)
|
|
104
97
|
exception.error_number if exception.respond_to?(:error_number)
|
|
105
98
|
end
|
|
@@ -5,9 +5,8 @@ module ActiveRecord
|
|
|
5
5
|
class PoolConfig # :nodoc:
|
|
6
6
|
include MonitorMixin
|
|
7
7
|
|
|
8
|
-
attr_reader :db_config, :role, :shard
|
|
8
|
+
attr_reader :db_config, :role, :shard, :connection_descriptor
|
|
9
9
|
attr_writer :schema_reflection, :server_version
|
|
10
|
-
attr_accessor :connection_class
|
|
11
10
|
|
|
12
11
|
def schema_reflection
|
|
13
12
|
@schema_reflection ||= SchemaReflection.new(db_config.lazy_schema_cache_path)
|
|
@@ -29,7 +28,7 @@ module ActiveRecord
|
|
|
29
28
|
def initialize(connection_class, db_config, role, shard)
|
|
30
29
|
super()
|
|
31
30
|
@server_version = nil
|
|
32
|
-
|
|
31
|
+
self.connection_descriptor = connection_class
|
|
33
32
|
@db_config = db_config
|
|
34
33
|
@role = role
|
|
35
34
|
@shard = shard
|
|
@@ -41,11 +40,12 @@ module ActiveRecord
|
|
|
41
40
|
@server_version || synchronize { @server_version ||= connection.get_database_version }
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
def
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
def connection_descriptor=(connection_descriptor)
|
|
44
|
+
case connection_descriptor
|
|
45
|
+
when ConnectionHandler::ConnectionDescriptor
|
|
46
|
+
@connection_descriptor = connection_descriptor
|
|
47
47
|
else
|
|
48
|
-
|
|
48
|
+
@connection_descriptor = ConnectionHandler::ConnectionDescriptor.new(connection_descriptor.name, connection_descriptor.primary_class?)
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
@@ -65,7 +65,8 @@ module ActiveRecord
|
|
|
65
65
|
other.is_a?(Column) &&
|
|
66
66
|
super &&
|
|
67
67
|
identity? == other.identity? &&
|
|
68
|
-
serial? == other.serial?
|
|
68
|
+
serial? == other.serial? &&
|
|
69
|
+
virtual? == other.virtual?
|
|
69
70
|
end
|
|
70
71
|
alias :eql? :==
|
|
71
72
|
|
|
@@ -73,7 +74,8 @@ module ActiveRecord
|
|
|
73
74
|
Column.hash ^
|
|
74
75
|
super.hash ^
|
|
75
76
|
identity?.hash ^
|
|
76
|
-
serial?.hash
|
|
77
|
+
serial?.hash ^
|
|
78
|
+
virtual?.hash
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
end
|
|
@@ -12,16 +12,8 @@ module ActiveRecord
|
|
|
12
12
|
|
|
13
13
|
# Queries the database and returns the results in an Array-like object
|
|
14
14
|
def query(sql, name = nil) # :nodoc:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
log(sql, name) do |notification_payload|
|
|
18
|
-
with_raw_connection do |conn|
|
|
19
|
-
result = conn.async_exec(sql).map_types!(@type_map_for_results).values
|
|
20
|
-
verified!
|
|
21
|
-
notification_payload[:row_count] = result.count
|
|
22
|
-
result
|
|
23
|
-
end
|
|
24
|
-
end
|
|
15
|
+
result = internal_execute(sql, name)
|
|
16
|
+
result.map_types!(@type_map_for_results).values
|
|
25
17
|
end
|
|
26
18
|
|
|
27
19
|
READ_QUERY = ActiveRecord::ConnectionAdapters::AbstractAdapter.build_read_query_regexp(
|
|
@@ -50,36 +42,6 @@ module ActiveRecord
|
|
|
50
42
|
@notice_receiver_sql_warnings = []
|
|
51
43
|
end
|
|
52
44
|
|
|
53
|
-
def raw_execute(sql, name, async: false, allow_retry: false, materialize_transactions: true)
|
|
54
|
-
log(sql, name, async: async) do |notification_payload|
|
|
55
|
-
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
|
|
56
|
-
result = conn.async_exec(sql)
|
|
57
|
-
verified!
|
|
58
|
-
handle_warnings(result)
|
|
59
|
-
notification_payload[:row_count] = result.count
|
|
60
|
-
result
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false, allow_retry: false, materialize_transactions: true) # :nodoc:
|
|
66
|
-
execute_and_clear(sql, name, binds, prepare: prepare, async: async, allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |result|
|
|
67
|
-
types = {}
|
|
68
|
-
fields = result.fields
|
|
69
|
-
fields.each_with_index do |fname, i|
|
|
70
|
-
ftype = result.ftype i
|
|
71
|
-
fmod = result.fmod i
|
|
72
|
-
types[fname] = types[i] = get_oid_type(ftype, fmod, fname)
|
|
73
|
-
end
|
|
74
|
-
build_result(columns: fields, rows: result.values, column_types: types.freeze)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def exec_delete(sql, name = nil, binds = []) # :nodoc:
|
|
79
|
-
execute_and_clear(sql, name, binds) { |result| result.cmd_tuples }
|
|
80
|
-
end
|
|
81
|
-
alias :exec_update :exec_delete
|
|
82
|
-
|
|
83
45
|
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil, returning: nil) # :nodoc:
|
|
84
46
|
if use_insert_returning? || pk == false
|
|
85
47
|
super
|
|
@@ -165,13 +127,80 @@ module ActiveRecord
|
|
|
165
127
|
def cancel_any_running_query
|
|
166
128
|
return if @raw_connection.nil? || IDLE_TRANSACTION_STATUSES.include?(@raw_connection.transaction_status)
|
|
167
129
|
|
|
168
|
-
@raw_connection.cancel
|
|
130
|
+
# Skip @raw_connection.cancel (PG::Connection#cancel) when using libpq >= 18 with pg < 1.6.0,
|
|
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
|
|
169
138
|
@raw_connection.block
|
|
170
139
|
rescue PG::Error
|
|
171
140
|
end
|
|
172
141
|
|
|
173
|
-
def
|
|
174
|
-
|
|
142
|
+
def perform_query(raw_connection, sql, binds, type_casted_binds, prepare:, notification_payload:, batch: false)
|
|
143
|
+
update_typemap_for_default_timezone
|
|
144
|
+
result = if prepare
|
|
145
|
+
begin
|
|
146
|
+
stmt_key = prepare_statement(sql, binds, raw_connection)
|
|
147
|
+
notification_payload[:statement_name] = stmt_key
|
|
148
|
+
raw_connection.exec_prepared(stmt_key, type_casted_binds)
|
|
149
|
+
rescue PG::FeatureNotSupported => error
|
|
150
|
+
if is_cached_plan_failure?(error)
|
|
151
|
+
# Nothing we can do if we are in a transaction because all commands
|
|
152
|
+
# will raise InFailedSQLTransaction
|
|
153
|
+
if in_transaction?
|
|
154
|
+
raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
|
|
155
|
+
else
|
|
156
|
+
@lock.synchronize do
|
|
157
|
+
# outside of transactions we can simply flush this query and retry
|
|
158
|
+
@statements.delete sql_key(sql)
|
|
159
|
+
end
|
|
160
|
+
retry
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
raise
|
|
165
|
+
end
|
|
166
|
+
elsif binds.nil? || binds.empty?
|
|
167
|
+
raw_connection.async_exec(sql)
|
|
168
|
+
else
|
|
169
|
+
raw_connection.exec_params(sql, type_casted_binds)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
verified!
|
|
173
|
+
handle_warnings(result)
|
|
174
|
+
notification_payload[:row_count] = result.ntuples
|
|
175
|
+
result
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def cast_result(result)
|
|
179
|
+
if result.fields.empty?
|
|
180
|
+
result.clear
|
|
181
|
+
return ActiveRecord::Result.empty
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
types = {}
|
|
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)
|
|
190
|
+
end
|
|
191
|
+
ar_result = ActiveRecord::Result.new(fields, result.values, types.freeze)
|
|
192
|
+
result.clear
|
|
193
|
+
ar_result
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def affected_rows(result)
|
|
197
|
+
affected_rows = result.cmd_tuples
|
|
198
|
+
result.clear
|
|
199
|
+
affected_rows
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
203
|
+
raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
|
|
175
204
|
end
|
|
176
205
|
|
|
177
206
|
def build_truncate_statements(table_names)
|
|
@@ -25,6 +25,10 @@ module ActiveRecord
|
|
|
25
25
|
build_point(x, y)
|
|
26
26
|
when ::Array
|
|
27
27
|
build_point(*value)
|
|
28
|
+
when ::Hash
|
|
29
|
+
return if value.blank?
|
|
30
|
+
|
|
31
|
+
build_point(*values_array_from_hash(value))
|
|
28
32
|
else
|
|
29
33
|
value
|
|
30
34
|
end
|
|
@@ -36,6 +40,8 @@ module ActiveRecord
|
|
|
36
40
|
"(#{number_for_point(value.x)},#{number_for_point(value.y)})"
|
|
37
41
|
when ::Array
|
|
38
42
|
serialize(build_point(*value))
|
|
43
|
+
when ::Hash
|
|
44
|
+
serialize(build_point(*values_array_from_hash(value)))
|
|
39
45
|
else
|
|
40
46
|
super
|
|
41
47
|
end
|
|
@@ -57,6 +63,10 @@ module ActiveRecord
|
|
|
57
63
|
def build_point(x, y)
|
|
58
64
|
ActiveRecord::Point.new(Float(x), Float(y))
|
|
59
65
|
end
|
|
66
|
+
|
|
67
|
+
def values_array_from_hash(value)
|
|
68
|
+
[value.values_at(:x, "x").compact.first, value.values_at(:y, "y").compact.first]
|
|
69
|
+
end
|
|
60
70
|
end
|
|
61
71
|
end
|
|
62
72
|
end
|
|
@@ -45,12 +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'' AND connamespace::regnamespace = ''%I''::regnamespace; ALTER TABLE %I.%I VALIDATE CONSTRAINT %I;',
|
|
48
|
+
'UPDATE pg_catalog.pg_constraint SET convalidated=false WHERE conname = ''%1$I'' AND connamespace::regnamespace = ''%2$I''::regnamespace; ALTER TABLE %2$I.%3$I VALIDATE CONSTRAINT %1$I;',
|
|
49
49
|
constraint_name,
|
|
50
50
|
table_schema,
|
|
51
|
-
|
|
52
|
-
table_name,
|
|
53
|
-
constraint_name
|
|
51
|
+
table_name
|
|
54
52
|
) AS constraint_check
|
|
55
53
|
FROM information_schema.table_constraints WHERE constraint_type = 'FOREIGN KEY'
|
|
56
54
|
)
|
|
@@ -11,14 +11,11 @@ module ActiveRecord
|
|
|
11
11
|
sql = super
|
|
12
12
|
sql << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ")
|
|
13
13
|
sql << o.exclusion_constraint_adds.map { |con| visit_AddExclusionConstraint con }.join(" ")
|
|
14
|
-
sql << o.exclusion_constraint_drops.map { |con| visit_DropExclusionConstraint con }.join(" ")
|
|
15
14
|
sql << o.unique_constraint_adds.map { |con| visit_AddUniqueConstraint con }.join(" ")
|
|
16
|
-
sql << o.unique_constraint_drops.map { |con| visit_DropUniqueConstraint con }.join(" ")
|
|
17
15
|
end
|
|
18
16
|
|
|
19
17
|
def visit_AddForeignKey(o)
|
|
20
18
|
super.dup.tap do |sql|
|
|
21
|
-
sql << " DEFERRABLE INITIALLY #{o.options[:deferrable].to_s.upcase}" if o.deferrable
|
|
22
19
|
sql << " NOT VALID" unless o.validate?
|
|
23
20
|
end
|
|
24
21
|
end
|
|
@@ -55,6 +52,7 @@ module ActiveRecord
|
|
|
55
52
|
sql = ["CONSTRAINT"]
|
|
56
53
|
sql << quote_column_name(o.name)
|
|
57
54
|
sql << "UNIQUE"
|
|
55
|
+
sql << "NULLS NOT DISTINCT" if supports_nulls_not_distinct? && o.nulls_not_distinct
|
|
58
56
|
|
|
59
57
|
if o.using_index
|
|
60
58
|
sql << "USING INDEX #{quote_column_name(o.using_index)}"
|
|
@@ -73,18 +71,10 @@ module ActiveRecord
|
|
|
73
71
|
"ADD #{accept(o)}"
|
|
74
72
|
end
|
|
75
73
|
|
|
76
|
-
def visit_DropExclusionConstraint(name)
|
|
77
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
78
|
-
end
|
|
79
|
-
|
|
80
74
|
def visit_AddUniqueConstraint(o)
|
|
81
75
|
"ADD #{accept(o)}"
|
|
82
76
|
end
|
|
83
77
|
|
|
84
|
-
def visit_DropUniqueConstraint(name)
|
|
85
|
-
"DROP CONSTRAINT #{quote_column_name(name)}"
|
|
86
|
-
end
|
|
87
|
-
|
|
88
78
|
def visit_ChangeColumnDefinition(o)
|
|
89
79
|
column = o.column
|
|
90
80
|
column.sql_type = type_to_sql(column.type, **column.options)
|