If the named portal binds a SQL `EXECUTE `, resolve the prepared statement, install a fresh portal for the inner statement (carrying the EXECUTE's actual parameter values), and return that portal's name so the caller can run `try_frontend_peek` against it. Only ever unrolls one level: the parser rejects `PREPARE foo AS EXECUTE bar` (matching Postgres), so the inner statement is guarante
(
&mut self,
portal_name: String,
outer_ctx_extra: &mut Option<ExecuteContextGuard>,
)
| 844 | /// the caller can thread it into `try_frontend_peek`, which reuses it |
| 845 | /// instead of taking its own. |
| 846 | async fn unroll_sql_execute( |
| 847 | &mut self, |
| 848 | portal_name: String, |
| 849 | outer_ctx_extra: &mut Option<ExecuteContextGuard>, |
| 850 | ) -> Result<(String, Option<Arc<Catalog>>), AdapterError> { |
| 851 | let (stmt, params, outer_logging, outer_lifecycle_timestamps) = { |
| 852 | let session = self.session.as_ref().expect("SessionClient invariant"); |
| 853 | let portal = match session.get_portal_unverified(&portal_name) { |
| 854 | Some(p) => p, |
| 855 | // No portal: let `try_frontend_peek` surface the |
| 856 | // standard "missing portal" error. |
| 857 | None => return Ok((portal_name, None)), |
| 858 | }; |
| 859 | match &portal.stmt { |
| 860 | Some(stmt) => ( |
| 861 | Arc::clone(stmt), |
| 862 | portal.parameters.clone(), |
| 863 | Arc::clone(&portal.logging), |
| 864 | portal.lifecycle_timestamps.clone(), |
| 865 | ), |
| 866 | None => return Ok((portal_name, None)), |
| 867 | } |
| 868 | }; |
| 869 | |
| 870 | // Only EXECUTE statements need unrolling. Bail out before taking a |
| 871 | // catalog snapshot in the (overwhelmingly common) non-EXECUTE case. |
| 872 | if !matches!(&*stmt, Statement::Execute(_)) { |
| 873 | return Ok((portal_name, None)); |
| 874 | } |
| 875 | |
| 876 | let catalog = self.catalog_snapshot("unroll_sql_execute").await; |
| 877 | |
| 878 | // Validate the outer EXECUTE portal against the (possibly newer) |
| 879 | // catalog: ensures the recorded portal description still matches |
| 880 | // what describing the EXECUTE would produce now. |
| 881 | { |
| 882 | let session = self.session.as_mut().expect("SessionClient invariant"); |
| 883 | Coordinator::verify_portal(&catalog, session, &portal_name)?; |
| 884 | } |
| 885 | |
| 886 | // Bump query_total for the outer EXECUTE itself. The inner |
| 887 | // statement gets its own increment inside `try_frontend_peek_inner` |
| 888 | // (or, on bailout, in the coordinator's `handle_execute`). |
| 889 | { |
| 890 | let session = self.session.as_ref().expect("SessionClient invariant"); |
| 891 | session |
| 892 | .metrics() |
| 893 | .query_total(&[ |
| 894 | metrics::session_type_label_value(session.user()), |
| 895 | metrics::statement_type_label_value(&stmt), |
| 896 | ]) |
| 897 | .inc(); |
| 898 | } |
| 899 | |
| 900 | // Begin EXECUTE-level statement logging up front, so that planning |
| 901 | // errors below produce an `Errored` end-event in |
| 902 | // `mz_statement_execution_history` rather than no entry at all. |
| 903 | // |
no test coverage detected