@@ -2,15 +2,9 @@ use crate::error::Error;
22use crate :: executor:: { Execute , Executor } ;
33use crate :: io:: { PortalId , StatementId } ;
44use crate :: logger:: QueryLogger ;
5- use crate :: message:: {
6- self , BackendMessageFormat , Bind , Close , CommandComplete , DataRow , ParameterDescription , Parse ,
7- ParseComplete , RowDescription ,
8- } ;
5+ use crate :: message:: { self , BackendMessageFormat , Bind , Close , CommandComplete , DataRow , ParameterDescription , Parse , ParseComplete , ReceivedMessage , RowDescription } ;
96use crate :: statement:: PgStatementMetadata ;
10- use crate :: {
11- statement:: PgStatement , PgArguments , PgConnection , PgQueryResult , PgRow , PgTypeInfo ,
12- PgValueFormat , Postgres ,
13- } ;
7+ use crate :: { statement:: PgStatement , PgArguments , PgConnection , PgDatabaseError , PgQueryResult , PgRow , PgTypeInfo , PgValueFormat , Postgres } ;
148use futures_core:: future:: BoxFuture ;
159use futures_core:: stream:: BoxStream ;
1610use futures_core:: Stream ;
@@ -19,6 +13,7 @@ use sqlx_core::arguments::Arguments;
1913use sqlx_core:: sql_str:: SqlStr ;
2014use sqlx_core:: Either ;
2115use std:: { pin:: pin, sync:: Arc } ;
16+ use sqlx_core:: connection:: Connection ;
2217
2318async fn prepare (
2419 conn : & mut PgConnection ,
@@ -196,22 +191,16 @@ impl PgConnection {
196191 Ok ( statement)
197192 }
198193
199- pub ( crate ) async fn run < ' e , ' c : ' e , ' q : ' e > (
194+ async fn try_get_or_prepare < ' e , ' c : ' e , ' q : ' e > (
200195 & ' c mut self ,
201- query : SqlStr ,
202- arguments : Option < PgArguments > ,
196+ sql : & str ,
197+ arguments : Option < & mut PgArguments > ,
203198 persistent : bool ,
204- metadata_opt : Option < Arc < PgStatementMetadata > > ,
205- ) -> Result < impl Stream < Item = Result < Either < PgQueryResult , PgRow > , Error > > + ' e , Error > {
206- let mut logger = QueryLogger :: new ( query, self . inner . log_settings . clone ( ) ) ;
207- let sql = logger. sql ( ) . as_str ( ) ;
208-
209- // before we continue, wait until we are "ready" to accept more queries
210- self . wait_until_ready ( ) . await ?;
211-
212- let mut metadata: Arc < PgStatementMetadata > ;
199+ metadata_opt : Option < Arc < PgStatementMetadata > >
200+ ) -> Result < ( PgValueFormat , Arc < PgStatementMetadata > ) , Error > {
201+ let metadata: Arc < PgStatementMetadata > ;
213202
214- let format = if let Some ( mut arguments) = arguments {
203+ let format = if let Some ( arguments) = arguments {
215204 // Check this before we write anything to the stream.
216205 //
217206 // Note: Postgres actually interprets this value as unsigned,
@@ -292,10 +281,62 @@ impl PgConnection {
292281
293282 self . inner . stream . flush ( ) . await ?;
294283
284+ Ok ( ( format, metadata) )
285+ }
286+
287+ pub ( crate ) async fn run < ' e , ' c : ' e , ' q : ' e > (
288+ & ' c mut self ,
289+ query : SqlStr ,
290+ mut arguments : Option < PgArguments > ,
291+ persistent : bool ,
292+ metadata_opt : Option < Arc < PgStatementMetadata > > ,
293+ ) -> Result < impl Stream < Item = Result < Either < PgQueryResult , PgRow > , Error > > + ' e , Error > {
294+ let mut logger = QueryLogger :: new ( query, self . inner . log_settings . clone ( ) ) ;
295+ let sql = logger. sql ( ) . as_str ( ) ;
296+
297+ // before we continue, wait until we are "ready" to accept more queries
298+ self . wait_until_ready ( ) . await ?;
299+
300+ let ( mut format, mut metadata) = self . try_get_or_prepare (
301+ sql,
302+ arguments. as_mut ( ) ,
303+ persistent,
304+ metadata_opt. clone ( )
305+ ) . await ?;
306+
307+ let mut message = match self . inner . stream . recv ( ) . await {
308+ Ok ( msg) => msg,
309+ Err ( err) => {
310+ if let Some ( clear_backend_cache) = check_stale_plan ( & err) {
311+ // Save transaction mode. It will be lost after invalidating
312+ let is_in_tx = self . in_transaction ( ) ;
313+
314+ self . invalidate_cached_statement ( sql, clear_backend_cache) . await ?;
315+
316+ // If we were in transaction mode we can't retry statement,
317+ // so we can immediately return err
318+ if is_in_tx {
319+ return Err ( err)
320+ }
321+
322+ // Otherwise we can retry statement in hope everything is ok.
323+ ( format, metadata) = self . try_get_or_prepare (
324+ sql,
325+ // It should be safe to retry `patch` on the same arguments
326+ arguments. as_mut ( ) ,
327+ persistent,
328+ metadata_opt. clone ( )
329+ ) . await ?;
330+
331+ self . inner . stream . recv ( ) . await ?
332+ } else {
333+ return Err ( err)
334+ }
335+ }
336+ } ;
337+
295338 Ok ( try_stream ! {
296339 loop {
297- let message = self . inner. stream. recv( ) . await ?;
298-
299340 match message. format {
300341 BackendMessageFormat :: BindComplete
301342 | BackendMessageFormat :: ParseComplete
@@ -369,6 +410,8 @@ impl PgConnection {
369410 ) ) ;
370411 }
371412 }
413+
414+ message = self . inner. stream. recv( ) . await ?;
372415 }
373416
374417 Ok ( ( ) )
@@ -485,3 +528,27 @@ impl<'c> Executor<'c> for &'c mut PgConnection {
485528 } )
486529 }
487530}
531+
532+ // Returns:
533+ // - `None` - if not 'stale query plan'
534+ // - `Some(false)` - if it is stale plan, but we don't need to deallocate objects on backend.
535+ // It can happen because of `DISCARD ALL`, `DEALLOCATE` or due to pgbouncer in
536+ // transaction pooling mode
537+ // - `Some(true)` - if we should invalidate both backend and frontend caches
538+ fn check_stale_plan ( error : & Error ) -> Option < bool > {
539+ let Some ( db_err) = error. as_database_error ( ) else {
540+ return None ;
541+ } ;
542+ let Some ( pg) = db_err. try_downcast_ref :: < PgDatabaseError > ( ) else {
543+ return None ;
544+ } ;
545+
546+ match ( pg. code ( ) , pg. routine ( ) ) {
547+ // "cached plan must not change result type"
548+ ( "0A000" , Some ( "RevalidateCachedQuery" ) ) => Some ( true ) ,
549+ // DISCARD ALL / DEALLOCATE / pgbouncer
550+ ( "26000" , _) => Some ( false ) ,
551+ _ => None ,
552+ }
553+ }
554+
0 commit comments