Skip to content

Commit f9f7deb

Browse files
committed
Refactor PostgresqlAdapter#raw_execute to be less unique
The postgres adapter used to be more complex than the others to be able to pass the prepared statement key to the `log` method. If we instead add it to the payload later, we can simplify the method further and it opens the door to refactor `log` and `with_raw_connection` out of `raw_execute`.
1 parent fd24e5b commit f9f7deb

File tree

5 files changed

+35
-45
lines changed

5 files changed

+35
-45
lines changed

activerecord/lib/active_record/connection_adapters/abstract_adapter.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,14 +1118,13 @@ def translate_exception_class(native_error, sql, binds)
11181118
active_record_error
11191119
end
11201120

1121-
def log(sql, name = "SQL", binds = [], type_casted_binds = [], statement_name = nil, async: false, &block) # :doc:
1121+
def log(sql, name = "SQL", binds = [], type_casted_binds = [], async: false, &block) # :doc:
11221122
@instrumenter.instrument(
11231123
"sql.active_record",
11241124
sql: sql,
11251125
name: name,
11261126
binds: binds,
11271127
type_casted_binds: type_casted_binds,
1128-
statement_name: statement_name,
11291128
async: async,
11301129
connection: self,
11311130
transaction: current_transaction.user_transaction.presence,

activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -137,49 +137,40 @@ def raw_execute(sql, name, binds = nil, prepare: false, async: false, allow_retr
137137

138138
type_casted_binds = type_casted_binds(binds)
139139

140-
begin
140+
log(sql, name, binds, type_casted_binds, async: async) do |notification_payload|
141141
with_raw_connection(allow_retry: allow_retry, materialize_transactions: materialize_transactions) do |conn|
142-
stmt_key = prepare_statement(sql, binds, conn) if prepare
143-
144-
log(sql, name, binds, type_casted_binds, stmt_key, async: async) do |notification_payload|
142+
result = if prepare
145143
begin
146-
result = if prepare
147-
conn.exec_prepared(stmt_key, type_casted_binds)
148-
elsif without_prepared_statement?(binds)
149-
conn.async_exec(sql)
150-
else
151-
conn.exec_params(sql, type_casted_binds)
144+
stmt_key = prepare_statement(sql, binds, conn)
145+
notification_payload[:statement_name] = stmt_key
146+
conn.exec_prepared(stmt_key, type_casted_binds)
147+
rescue PG::FeatureNotSupported => error
148+
if is_cached_plan_failure?(error)
149+
# Nothing we can do if we are in a transaction because all commands
150+
# will raise InFailedSQLTransaction
151+
if in_transaction?
152+
raise PreparedStatementCacheExpired.new(error.message, connection_pool: @pool)
153+
else
154+
@lock.synchronize do
155+
# outside of transactions we can simply flush this query and retry
156+
@statements.delete sql_key(sql)
157+
end
158+
retry
159+
end
152160
end
153-
rescue => original_exception
154-
# Contrary to all other adapters we have to enter `with_raw_connection` before `log`
155-
# so that we can prepare the statement and pass the key to `log`.
156-
# So we need to translate exceptions ourselves.
157-
raise translate_exception_class(original_exception, sql, binds)
158-
end
159161

160-
verified!
161-
handle_warnings(result)
162-
notification_payload[:row_count] = result.count
163-
result
164-
end
165-
end
166-
rescue ActiveRecord::StatementInvalid => error
167-
if prepare
168-
raise unless is_cached_plan_failure?(error)
169-
170-
# Nothing we can do if we are in a transaction because all commands
171-
# will raise InFailedSQLTransaction
172-
if in_transaction?
173-
raise ActiveRecord::PreparedStatementCacheExpired.new(error.cause.message, connection_pool: @pool)
174-
else
175-
@lock.synchronize do
176-
# outside of transactions we can simply flush this query and retry
177-
@statements.delete sql_key(sql)
162+
raise
178163
end
179-
retry
164+
elsif without_prepared_statement?(binds)
165+
conn.async_exec(sql)
166+
else
167+
conn.exec_params(sql, type_casted_binds)
180168
end
181-
else
182-
raise
169+
170+
verified!
171+
handle_warnings(result)
172+
notification_payload[:row_count] = result.count
173+
result
183174
end
184175
end
185176
end

activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,7 @@ def load_types_queries(initializer, oids)
872872
#
873873
# Check here for more details:
874874
# https://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
875-
def is_cached_plan_failure?(e)
876-
pgerror = e.cause
875+
def is_cached_plan_failure?(pgerror)
877876
pgerror.result.result_error_field(PG::PG_DIAG_SQLSTATE) == FEATURE_NOT_SUPPORTED &&
878877
pgerror.result.result_error_field(PG::PG_DIAG_SOURCE_FUNCTION) == "RevalidateCachedQuery"
879878
rescue

activerecord/test/cases/adapters/postgresql/connection_test.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,11 @@ def test_schema_names_logs_name
130130
def test_statement_key_is_logged
131131
bind = Relation::QueryAttribute.new(nil, 1, Type::Value.new)
132132
@connection.exec_query("SELECT $1::integer", "SQL", [bind], prepare: true)
133-
name = @subscriber.payloads.last[:statement_name]
134-
assert name
133+
134+
payload = @subscriber.payloads.find { |p| p[:sql] == "SELECT $1::integer" }
135+
name = payload[:statement_name]
136+
assert_not_nil name
137+
135138
res = @connection.exec_query("EXPLAIN (FORMAT JSON) EXECUTE #{name}(1)")
136139
plan = res.column_types["QUERY PLAN"].deserialize res.rows.first.first
137140
assert_operator plan.length, :>, 0

activerecord/test/cases/adapters/trilogy/trilogy_adapter_test.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,6 @@ class TrilogyAdapterTest < ActiveRecord::TrilogyTestCase
222222
assert_includes payload, :type_casted_binds
223223
assert_equal [], payload[:type_casted_binds]
224224

225-
# :stament_name is always nil and never set 🤷‍♂️
226-
assert_includes payload, :statement_name
227225
assert_nil payload[:statement_name]
228226

229227
assert_not_includes payload, :cached

0 commit comments

Comments
 (0)