@@ -211,36 +211,52 @@ export class PostgresController {
211211
212212 const statRow = ( statsResult as unknown as any [ ] ) [ 0 ] ;
213213
214- // Try to get EXPLAIN plan (may fail for queries with parameters)
214+ // Try to get EXPLAIN plan for pev2 (https://github.com/dalibo/pev2)
215+ // We use ANALYZE for actual execution stats, but wrap in a transaction with ROLLBACK
216+ // to prevent any data modifications from DML queries
215217 let explainPlan : string | null = null ;
216218 try {
217- // First try with generic plan for parameterized queries
218- const explainResult = await this . postgres . query < {
219- "QUERY PLAN" : string ;
220- } > ( `
221- EXPLAIN (FORMAT TEXT, GENERIC_PLAN true) ${ statRow . query }
222- ` ) ;
223- explainPlan = ( explainResult as unknown as any [ ] )
224- . map ( ( row ) => row [ "QUERY PLAN" ] )
225- . join ( "\n" ) ;
219+ // First try with full ANALYZE in a rolled-back transaction for safety
220+ // This gives us actual execution stats for pev2 without persisting changes
221+ await this . postgres . query ( "BEGIN" ) ;
222+ try {
223+ const explainResult = await this . postgres . query < {
224+ "QUERY PLAN" : any ;
225+ } > ( `
226+ EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) ${ statRow . query }
227+ ` ) ;
228+ await this . postgres . query ( "ROLLBACK" ) ;
229+ // Format the JSON nicely for pev2
230+ const jsonPlan = ( explainResult as unknown as any [ ] ) [ 0 ] [ "QUERY PLAN" ] ;
231+ explainPlan = JSON . stringify ( jsonPlan , null , 2 ) ;
232+ } catch ( analyzeError ) {
233+ // Make sure to rollback on error
234+ await this . postgres . query ( "ROLLBACK" ) ;
235+ throw analyzeError ;
236+ }
226237 } catch ( error ) {
227- // If generic plan fails, try without it (works for non-parameterized queries )
238+ // If ANALYZE fails, try without it (still good for pev2, just no actual stats )
228239 try {
229240 const explainResult = await this . postgres . query < {
230- "QUERY PLAN" : string ;
241+ "QUERY PLAN" : any ;
231242 } > ( `
232- EXPLAIN (FORMAT TEXT ) ${ statRow . query }
243+ EXPLAIN (COSTS, VERBOSE, BUFFERS, FORMAT JSON ) ${ statRow . query }
233244 ` ) ;
234- explainPlan = ( explainResult as unknown as any [ ] )
235- . map ( ( row ) => row [ "QUERY PLAN" ] )
236- . join ( "\n" ) ;
237- } catch ( innerError ) {
238- // EXPLAIN failed - this is common for parameterized queries from pg_stat_statements
239- // Return a helpful message instead of null
240- explainPlan =
241- "EXPLAIN plan cannot be generated for parameterized queries.\n\n" +
242- "The query contains parameter placeholders ($1, $2, etc.) that cannot be explained without actual values.\n\n" +
243- "To see the execution plan, run EXPLAIN with actual parameter values in psql or your database client." ;
245+ const jsonPlan = ( explainResult as unknown as any [ ] ) [ 0 ] [ "QUERY PLAN" ] ;
246+ explainPlan = JSON . stringify ( jsonPlan , null , 2 ) ;
247+ } catch {
248+ // Fall back to generic plan for parameterized queries
249+ try {
250+ const explainResult = await this . postgres . query < {
251+ "QUERY PLAN" : any ;
252+ } > ( `
253+ EXPLAIN (FORMAT JSON, GENERIC_PLAN true) ${ statRow . query }
254+ ` ) ;
255+ const jsonPlan = ( explainResult as unknown as any [ ] ) [ 0 ] [ "QUERY PLAN" ] ;
256+ explainPlan = JSON . stringify ( jsonPlan , null , 2 ) ;
257+ } catch {
258+ // not able to get explain plan
259+ }
244260 }
245261 }
246262
0 commit comments