activerecord 7.2.3 → 8.1.3
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 +612 -1055
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +2 -1
- data/lib/active_record/associations/association.rb +35 -11
- data/lib/active_record/associations/builder/association.rb +23 -11
- 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_association.rb +1 -1
- data/lib/active_record/associations/collection_proxy.rb +22 -4
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +3 -0
- data/lib/active_record/associations/has_many_through_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +4 -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/preloader/branch.rb +1 -0
- data/lib/active_record/associations/singular_association.rb +8 -3
- data/lib/active_record/associations.rb +192 -24
- 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/serialization.rb +16 -3
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attributes.rb +3 -0
- data/lib/active_record/autosave_association.rb +69 -27
- data/lib/active_record/base.rb +1 -2
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +35 -28
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +51 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +412 -88
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +137 -75
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +27 -5
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -25
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +32 -35
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +122 -32
- data/lib/active_record/connection_adapters/abstract/transaction.rb +40 -8
- data/lib/active_record/connection_adapters/abstract_adapter.rb +150 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +63 -52
- 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/quoting.rb +0 -8
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +73 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +89 -94
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +2 -10
- data/lib/active_record/connection_adapters/pool_config.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/column.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- 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/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +14 -33
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +71 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +139 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +78 -105
- data/lib/active_record/connection_adapters/schema_cache.rb +3 -5
- data/lib/active_record/connection_adapters/sqlite3/column.rb +8 -2
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +90 -98
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +0 -8
- 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 +13 -14
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +102 -37
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +38 -67
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +1 -18
- data/lib/active_record/connection_adapters.rb +1 -56
- data/lib/active_record/connection_handling.rb +25 -2
- data/lib/active_record/core.rb +33 -17
- data/lib/active_record/counter_cache.rb +33 -8
- data/lib/active_record/database_configurations/database_config.rb +9 -1
- data/lib/active_record/database_configurations/hash_config.rb +67 -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/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +8 -8
- data/lib/active_record/encryption/encrypted_attribute_type.rb +11 -2
- data/lib/active_record/encryption/encryptor.rb +28 -8
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +33 -30
- data/lib/active_record/errors.rb +33 -9
- 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 -4
- data/lib/active_record/future_result.rb +15 -9
- data/lib/active_record/gem_version.rb +2 -2
- data/lib/active_record/inheritance.rb +1 -1
- data/lib/active_record/insert_all.rb +14 -9
- data/lib/active_record/locking/optimistic.rb +8 -1
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -13
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +45 -12
- data/lib/active_record/migration/compatibility.rb +37 -24
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration.rb +48 -42
- data/lib/active_record/model_schema.rb +38 -13
- data/lib/active_record/nested_attributes.rb +6 -6
- data/lib/active_record/persistence.rb +162 -133
- data/lib/active_record/query_cache.rb +22 -15
- data/lib/active_record/query_logs.rb +100 -52
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +8 -8
- data/lib/active_record/railtie.rb +35 -30
- data/lib/active_record/railties/controller_runtime.rb +11 -6
- data/lib/active_record/railties/databases.rake +26 -38
- 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 +53 -21
- data/lib/active_record/relation/batches/batch_enumerator.rb +4 -3
- data/lib/active_record/relation/batches.rb +147 -73
- data/lib/active_record/relation/calculations.rb +52 -40
- data/lib/active_record/relation/delegation.rb +25 -15
- data/lib/active_record/relation/finder_methods.rb +40 -24
- 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/association_query_value.rb +9 -9
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +22 -7
- data/lib/active_record/relation/query_attribute.rb +3 -1
- data/lib/active_record/relation/query_methods.rb +140 -86
- data/lib/active_record/relation/spawn_methods.rb +7 -7
- data/lib/active_record/relation/where_clause.rb +2 -9
- data/lib/active_record/relation.rb +107 -75
- data/lib/active_record/result.rb +109 -24
- data/lib/active_record/runtime_registry.rb +42 -58
- data/lib/active_record/sanitization.rb +9 -6
- data/lib/active_record/schema_dumper.rb +18 -11
- data/lib/active_record/schema_migration.rb +2 -1
- data/lib/active_record/scoping/named.rb +5 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/signed_id.rb +43 -15
- data/lib/active_record/statement_cache.rb +24 -20
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +6 -23
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +85 -85
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +7 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +39 -2
- data/lib/active_record/testing/query_assertions.rb +8 -2
- data/lib/active_record/timestamp.rb +4 -2
- data/lib/active_record/token_for.rb +1 -1
- data/lib/active_record/transaction.rb +2 -5
- data/lib/active_record/transactions.rb +37 -16
- 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 +13 -2
- data/lib/active_record/type/serialized.rb +16 -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/validations/uniqueness.rb +8 -8
- data/lib/active_record.rb +84 -49
- data/lib/arel/alias_predication.rb +2 -0
- 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/crud.rb +6 -11
- data/lib/arel/nodes/binary.rb +1 -1
- 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 +2 -2
- data/lib/arel/nodes/sql_literal.rb +1 -1
- data/lib/arel/nodes.rb +0 -2
- data/lib/arel/predications.rb +1 -3
- data/lib/arel/select_manager.rb +7 -2
- data/lib/arel/table.rb +3 -7
- 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 +16 -13
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -163
- 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,107 @@ 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
|
-
|
|
51
|
+
result = nil
|
|
52
|
+
if binds.nil? || binds.empty?
|
|
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
|
|
60
|
+
elsif prepare
|
|
61
|
+
retry_count = 1
|
|
62
|
+
begin
|
|
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
|
|
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
|
|
79
|
+
raise
|
|
80
|
+
end
|
|
81
|
+
else
|
|
82
|
+
stmt = raw_connection.prepare(sql)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
result = stmt.execute(*type_casted_binds)
|
|
86
|
+
@affected_rows_before_warnings = stmt.affected_rows
|
|
87
|
+
|
|
88
|
+
# Ref: https://github.com/brianmario/mysql2/pull/1383
|
|
89
|
+
# by eagerly closing uncached prepared statements, we also reduce the chances of
|
|
90
|
+
# that bug happening. It can still happen if `#execute` is used as we have no callback
|
|
91
|
+
# to eagerly close the statement.
|
|
92
|
+
if result
|
|
93
|
+
result.instance_variable_set(:@_ar_stmt_to_close, stmt)
|
|
94
|
+
else
|
|
95
|
+
stmt.close
|
|
96
|
+
end
|
|
97
|
+
rescue ::Mysql2::Error
|
|
98
|
+
stmt.close
|
|
99
|
+
raise
|
|
100
|
+
end
|
|
91
101
|
end
|
|
92
|
-
end
|
|
93
102
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
end
|
|
103
|
+
notification_payload[:affected_rows] = @affected_rows_before_warnings
|
|
104
|
+
notification_payload[:row_count] = result&.size || 0
|
|
105
|
+
|
|
106
|
+
raw_connection.abandon_results!
|
|
107
|
+
|
|
108
|
+
verified!
|
|
109
|
+
result
|
|
110
|
+
ensure
|
|
111
|
+
if reset_multi_statement && active?
|
|
112
|
+
raw_connection.set_server_option(::Mysql2::Client::OPTION_MULTI_STATEMENTS_OFF)
|
|
105
113
|
end
|
|
106
114
|
end
|
|
107
115
|
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
def cast_result(raw_result)
|
|
117
|
+
return ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings) if raw_result.nil?
|
|
118
|
+
|
|
119
|
+
fields = raw_result.fields
|
|
111
120
|
|
|
112
|
-
|
|
121
|
+
result = if fields.empty?
|
|
122
|
+
ActiveRecord::Result.empty(affected_rows: @affected_rows_before_warnings)
|
|
123
|
+
else
|
|
124
|
+
ActiveRecord::Result.new(fields, raw_result.to_a)
|
|
125
|
+
end
|
|
113
126
|
|
|
114
|
-
|
|
127
|
+
free_raw_result(raw_result)
|
|
115
128
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
sync_timezone_changes(conn)
|
|
129
|
+
result
|
|
130
|
+
end
|
|
119
131
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
stmt = conn.prepare(sql)
|
|
124
|
-
end
|
|
132
|
+
def affected_rows(raw_result)
|
|
133
|
+
free_raw_result(raw_result) if raw_result
|
|
125
134
|
|
|
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
|
|
135
|
+
@affected_rows_before_warnings
|
|
136
|
+
end
|
|
140
137
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
ret
|
|
146
|
-
end
|
|
138
|
+
def free_raw_result(raw_result)
|
|
139
|
+
raw_result.free
|
|
140
|
+
if stmt = raw_result.instance_variable_get(:@_ar_stmt_to_close)
|
|
141
|
+
stmt.close
|
|
147
142
|
end
|
|
148
143
|
end
|
|
149
144
|
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
|
|
|
@@ -55,6 +56,7 @@ module ActiveRecord
|
|
|
55
56
|
def initialize(...)
|
|
56
57
|
super
|
|
57
58
|
|
|
59
|
+
@affected_rows_before_warnings = nil
|
|
58
60
|
@config[:flags] ||= 0
|
|
59
61
|
|
|
60
62
|
if @config[:flags].kind_of? Array
|
|
@@ -90,16 +92,6 @@ module ActiveRecord
|
|
|
90
92
|
true
|
|
91
93
|
end
|
|
92
94
|
|
|
93
|
-
# HELPER METHODS ===========================================
|
|
94
|
-
|
|
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
95
|
def error_number(exception)
|
|
104
96
|
exception.error_number if exception.respond_to?(:error_number)
|
|
105
97
|
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
|
|
|
@@ -30,6 +30,10 @@ module ActiveRecord
|
|
|
30
30
|
@generated.present?
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
def virtual_stored?
|
|
34
|
+
@generated == "s"
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
def has_default?
|
|
34
38
|
super && !virtual?
|
|
35
39
|
end
|
|
@@ -65,7 +69,8 @@ module ActiveRecord
|
|
|
65
69
|
other.is_a?(Column) &&
|
|
66
70
|
super &&
|
|
67
71
|
identity? == other.identity? &&
|
|
68
|
-
serial? == other.serial?
|
|
72
|
+
serial? == other.serial? &&
|
|
73
|
+
virtual? == other.virtual?
|
|
69
74
|
end
|
|
70
75
|
alias :eql? :==
|
|
71
76
|
|
|
@@ -73,7 +78,8 @@ module ActiveRecord
|
|
|
73
78
|
Column.hash ^
|
|
74
79
|
super.hash ^
|
|
75
80
|
identity?.hash ^
|
|
76
|
-
serial?.hash
|
|
81
|
+
serial?.hash ^
|
|
82
|
+
virtual?.hash
|
|
77
83
|
end
|
|
78
84
|
end
|
|
79
85
|
end
|
|
@@ -11,17 +11,9 @@ 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
|
-
|
|
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
|
|
14
|
+
def query(sql, name = nil, allow_retry: true, materialize_transactions: true) # :nodoc:
|
|
15
|
+
result = internal_execute(sql, name, allow_retry:, materialize_transactions:)
|
|
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,82 @@ 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
|
+
|
|
174
|
+
notification_payload[:affected_rows] = result.cmd_tuples
|
|
175
|
+
notification_payload[:row_count] = result.ntuples
|
|
176
|
+
result
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def cast_result(result)
|
|
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
|
|
190
|
+
|
|
191
|
+
ActiveRecord::Result.new(fields, result.values, types.freeze, affected_rows: result.cmd_tuples)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
result.clear
|
|
195
|
+
ar_result
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def affected_rows(result)
|
|
199
|
+
affected_rows = result.cmd_tuples
|
|
200
|
+
result.clear
|
|
201
|
+
affected_rows
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def execute_batch(statements, name = nil, **kwargs)
|
|
205
|
+
raw_execute(combine_multi_statements(statements), name, batch: true, **kwargs)
|
|
175
206
|
end
|
|
176
207
|
|
|
177
208
|
def build_truncate_statements(table_names)
|
|
@@ -191,7 +222,7 @@ module ActiveRecord
|
|
|
191
222
|
pk unless pk.is_a?(Array)
|
|
192
223
|
end
|
|
193
224
|
|
|
194
|
-
def handle_warnings(sql)
|
|
225
|
+
def handle_warnings(result, sql)
|
|
195
226
|
@notice_receiver_sql_warnings.each do |warning|
|
|
196
227
|
next if warning_ignored?(warning)
|
|
197
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)
|
|
@@ -65,7 +65,7 @@ module ActiveRecord
|
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def map(value, &block)
|
|
68
|
-
value.map
|
|
68
|
+
value.is_a?(::Array) ? value.map(&block) : subtype.map(value, &block)
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def changed_in_place?(raw_old_value, new_value)
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
)
|