activerecord 7.2.3 → 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +391 -958
- 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/preloader/association.rb +2 -2
- 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 +6 -15
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +90 -43
- 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 +34 -7
- data/lib/active_record/connection_adapters/abstract/transaction.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_adapter.rb +31 -43
- 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/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 +46 -96
- data/lib/active_record/connection_adapters/schema_cache.rb +1 -3
- 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 +13 -0
- 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 +13 -9
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/insert_all.rb +1 -1
- 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 +31 -11
- 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/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.rb +79 -61
- 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/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/table.rb +3 -7
- metadata +9 -10
- data/lib/active_record/relation/record_fetch_warning.rb +0 -52
|
@@ -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
|
|
|
@@ -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)
|
|
@@ -224,6 +224,10 @@ module ActiveRecord
|
|
|
224
224
|
options[:using_index]
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
+
def nulls_not_distinct
|
|
228
|
+
options[:nulls_not_distinct]
|
|
229
|
+
end
|
|
230
|
+
|
|
227
231
|
def export_name_on_schema_dump?
|
|
228
232
|
!ActiveRecord::SchemaDumper.unique_ignore_pattern.match?(name) if name
|
|
229
233
|
end
|
|
@@ -319,7 +323,7 @@ module ActiveRecord
|
|
|
319
323
|
|
|
320
324
|
# Adds a unique constraint.
|
|
321
325
|
#
|
|
322
|
-
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred)
|
|
326
|
+
# t.unique_constraint(:position, name: 'unique_position', deferrable: :deferred, nulls_not_distinct: true)
|
|
323
327
|
#
|
|
324
328
|
# See {connection.add_unique_constraint}[rdoc-ref:SchemaStatements#add_unique_constraint]
|
|
325
329
|
def unique_constraint(...)
|
|
@@ -358,15 +362,13 @@ module ActiveRecord
|
|
|
358
362
|
|
|
359
363
|
# = Active Record PostgreSQL Adapter Alter \Table
|
|
360
364
|
class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable
|
|
361
|
-
attr_reader :constraint_validations, :exclusion_constraint_adds, :
|
|
365
|
+
attr_reader :constraint_validations, :exclusion_constraint_adds, :unique_constraint_adds
|
|
362
366
|
|
|
363
367
|
def initialize(td)
|
|
364
368
|
super
|
|
365
369
|
@constraint_validations = []
|
|
366
370
|
@exclusion_constraint_adds = []
|
|
367
|
-
@exclusion_constraint_drops = []
|
|
368
371
|
@unique_constraint_adds = []
|
|
369
|
-
@unique_constraint_drops = []
|
|
370
372
|
end
|
|
371
373
|
|
|
372
374
|
def validate_constraint(name)
|
|
@@ -377,17 +379,9 @@ module ActiveRecord
|
|
|
377
379
|
@exclusion_constraint_adds << @td.new_exclusion_constraint_definition(expression, options)
|
|
378
380
|
end
|
|
379
381
|
|
|
380
|
-
def drop_exclusion_constraint(constraint_name)
|
|
381
|
-
@exclusion_constraint_drops << constraint_name
|
|
382
|
-
end
|
|
383
|
-
|
|
384
382
|
def add_unique_constraint(column_name, options)
|
|
385
383
|
@unique_constraint_adds << @td.new_unique_constraint_definition(column_name, options)
|
|
386
384
|
end
|
|
387
|
-
|
|
388
|
-
def drop_unique_constraint(unique_constraint_name)
|
|
389
|
-
@unique_constraint_drops << unique_constraint_name
|
|
390
|
-
end
|
|
391
385
|
end
|
|
392
386
|
end
|
|
393
387
|
end
|
|
@@ -68,6 +68,7 @@ module ActiveRecord
|
|
|
68
68
|
"t.unique_constraint #{unique_constraint.column.inspect}"
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
+
parts << "nulls_not_distinct: #{unique_constraint.nulls_not_distinct.inspect}" if unique_constraint.nulls_not_distinct
|
|
71
72
|
parts << "deferrable: #{unique_constraint.deferrable.inspect}" if unique_constraint.deferrable
|
|
72
73
|
|
|
73
74
|
if unique_constraint.export_name_on_schema_dump?
|
|
@@ -91,7 +92,7 @@ module ActiveRecord
|
|
|
91
92
|
spec = { type: schema_type(column).inspect }.merge!(spec)
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
spec[:enum_type] =
|
|
95
|
+
spec[:enum_type] = column.sql_type.inspect if column.enum?
|
|
95
96
|
|
|
96
97
|
spec
|
|
97
98
|
end
|