@@ -5446,16 +5446,44 @@ exec_stmt_execsql(PLtsql_execstate *estate,
54465446 SPI_freetuptable (SPI_tuptable );
54475447 exec_flush_insert_exec_temp_table (estate );
54485448 }
5449+
5450+ /*
5451+ * Drop temp table on SUCCESS path only.
5452+ * On error paths, the table might still be in use by active queries,
5453+ * and trying to drop it would fail. The temp table will be cleaned
5454+ * up when the session ends.
5455+ */
5456+ if (stmt -> insert_exec && estate -> insert_exec_temp_table != NULL )
5457+ {
5458+ exec_drop_insert_exec_temp_table (estate );
5459+ }
54495460 }
54505461 PG_FINALLY ();
54515462 {
54525463 original_query_string = NULL ;
54535464 is_schemabinding_view = true;
54545465
5455- /* Drop temp table on any exit (flush already happened on success path) */
5466+ /*
5467+ * On error paths, just clean up the estate fields without trying
5468+ * to drop the temp table. The table might still be in use by
5469+ * active queries, and trying to drop it would fail with an error
5470+ * that would mask the original error.
5471+ *
5472+ * The temp table will be cleaned up when the session ends.
5473+ */
54565474 if (stmt -> insert_exec && estate -> insert_exec_temp_table != NULL )
54575475 {
5458- exec_drop_insert_exec_temp_table (estate );
5476+ /* Free the TopMemoryContext allocations */
5477+ if (estate -> insert_exec_temp_table )
5478+ pfree (estate -> insert_exec_temp_table );
5479+ if (estate -> insert_exec_target_table )
5480+ pfree (estate -> insert_exec_target_table );
5481+ if (estate -> insert_exec_column_list )
5482+ pfree (estate -> insert_exec_column_list );
5483+ estate -> insert_exec_temp_table = NULL ;
5484+ estate -> insert_exec_target_table = NULL ;
5485+ estate -> insert_exec_column_list = NULL ;
5486+ estate -> insert_exec_identity_insert = false;
54595487 }
54605488
54615489 /* Clear INSERT EXEC rewrite context on any exit */
@@ -11639,29 +11667,49 @@ exec_flush_insert_exec_temp_table(PLtsql_execstate *estate)
1163911667 }
1164011668 PG_CATCH ();
1164111669 {
11642- ErrorData * edata ;
11643-
11644- MemoryContextSwitchTo (oldcontext );
11645- edata = CopyErrorData ();
11646- FlushErrorState ();
11647-
1164811670 /* Rollback subtransaction - this cleans up AfterTrigger depth */
11671+ MemoryContextSwitchTo (oldcontext );
1164911672 RollbackAndReleaseCurrentSubTransaction ();
1165011673 MemoryContextSwitchTo (oldcontext );
1165111674 CurrentResourceOwner = oldowner ;
1165211675
1165311676 /*
11654- * Drop the temp table NOW, while the parent transaction is still valid.
11655- * After ReThrowError, the transaction will be marked aborted and DDL
11656- * won't be possible. The drop function handles its own error catching.
11677+ * DO NOT try to drop the temp table here!
11678+ * The table may still be in use by active queries, and attempting
11679+ * to drop it would fail with "table is being used by active queries".
11680+ *
11681+ * Instead, just clear the estate fields so we don't try to drop again.
11682+ * The temp table will be cleaned up:
11683+ * 1. By the PG_FINALLY block in exec_stmt_exec (if transaction is still valid)
11684+ * 2. By PostgreSQL when the session ends (if transaction is aborted)
11685+ *
11686+ * We also need to free the TopMemoryContext allocations to prevent leaks.
1165711687 */
11658- exec_drop_insert_exec_temp_table (estate );
11688+ if (estate -> insert_exec_temp_table != NULL )
11689+ {
11690+ pfree (estate -> insert_exec_temp_table );
11691+ estate -> insert_exec_temp_table = NULL ;
11692+ }
11693+ if (estate -> insert_exec_target_table != NULL )
11694+ {
11695+ pfree (estate -> insert_exec_target_table );
11696+ estate -> insert_exec_target_table = NULL ;
11697+ }
11698+ if (estate -> insert_exec_column_list != NULL )
11699+ {
11700+ pfree (estate -> insert_exec_column_list );
11701+ estate -> insert_exec_column_list = NULL ;
11702+ }
11703+ estate -> insert_exec_identity_insert = false;
1165911704
1166011705 pfree (flush_query .data );
1166111706 pfree (tsql_target_ref );
1166211707
11663- /* Re-raise so T-SQL TRY/CATCH can handle it */
11664- ReThrowError (edata );
11708+ /*
11709+ * Re-raise the original error so T-SQL TRY/CATCH can handle it.
11710+ * The error is still on the stack after RollbackAndReleaseCurrentSubTransaction.
11711+ */
11712+ PG_RE_THROW ();
1166511713 }
1166611714 PG_END_TRY ();
1166711715
@@ -11673,14 +11721,22 @@ exec_flush_insert_exec_temp_table(PLtsql_execstate *estate)
1167311721 * exec_drop_insert_exec_temp_table
1167411722 *
1167511723 * Drop the temp buffer table and clean up estate fields.
11676- * Called on both success (after flush) and error paths.
11724+ *
11725+ * This function should only be called on the SUCCESS path (after flush).
11726+ * On error paths, just call exec_cleanup_insert_exec_state() to clean up
11727+ * the estate fields without trying to drop the table.
11728+ *
11729+ * The temp table is a session-local temp table, so PostgreSQL will
11730+ * automatically clean it up when the session ends.
1167711731 */
1167811732void
1167911733exec_drop_insert_exec_temp_table (PLtsql_execstate * estate )
1168011734{
1168111735 char * temp_table_name ;
1168211736 char * target_table_name ;
1168311737 char * column_list ;
11738+ MemoryContext oldcontext ;
11739+ ResourceOwner oldowner ;
1168411740
1168511741 if (estate -> insert_exec_temp_table == NULL )
1168611742 return ;
@@ -11697,7 +11753,17 @@ exec_drop_insert_exec_temp_table(PLtsql_execstate *estate)
1169711753 estate -> insert_exec_column_list = NULL ;
1169811754 estate -> insert_exec_identity_insert = false;
1169911755
11700- /* Ignore errors during cleanup — temp table may already be gone */
11756+ /*
11757+ * Try to drop the temp table using a subtransaction.
11758+ * This ensures that if the DROP fails, the failure is isolated
11759+ * and doesn't affect the main transaction.
11760+ */
11761+ oldcontext = CurrentMemoryContext ;
11762+ oldowner = CurrentResourceOwner ;
11763+
11764+ BeginInternalSubTransaction ("insert_exec_drop" );
11765+ MemoryContextSwitchTo (oldcontext );
11766+
1170111767 PG_TRY ();
1170211768 {
1170311769 char * drop_query = psprintf ("DROP TABLE IF EXISTS %s" , temp_table_name );
@@ -11710,10 +11776,34 @@ exec_drop_insert_exec_temp_table(PLtsql_execstate *estate)
1171011776 SPI_freetuptable (SPI_tuptable );
1171111777
1171211778 pfree (drop_query );
11779+
11780+ /* Success - release the subtransaction */
11781+ ReleaseCurrentSubTransaction ();
11782+ MemoryContextSwitchTo (oldcontext );
11783+ CurrentResourceOwner = oldowner ;
1171311784 }
1171411785 PG_CATCH ();
1171511786 {
11716- FlushErrorState ();
11787+ /*
11788+ * DROP failed - rollback the subtransaction.
11789+ * The temp table will be cleaned up when the session ends.
11790+ *
11791+ * CRITICAL: Do NOT call FlushErrorState() here!
11792+ * If we're being called from a PG_FINALLY block during error
11793+ * handling, there's already an error on the stack that needs
11794+ * to be preserved. Calling FlushErrorState() would clear it,
11795+ * causing "errstart was not called" errors downstream.
11796+ *
11797+ * The subtransaction rollback isolates the DROP failure.
11798+ * We just swallow the DROP error silently and let the original
11799+ * error (if any) continue to propagate.
11800+ */
11801+ MemoryContextSwitchTo (oldcontext );
11802+ RollbackAndReleaseCurrentSubTransaction ();
11803+ MemoryContextSwitchTo (oldcontext );
11804+ CurrentResourceOwner = oldowner ;
11805+
11806+ elog (DEBUG1 , "INSERT-EXEC-TEMP-TABLE: Drop failed, table will be cleaned up by session end" );
1171711807 }
1171811808 PG_END_TRY ();
1171911809
@@ -11999,14 +12089,14 @@ exec_rewritten_insert_exec(PLtsql_execstate *estate, PLtsql_stmt_execsql *stmt)
1199912089 {
1200012090 /* Always clear the skip_triggers flag */
1200112091 pltsql_set_insert_exec_skip_triggers (false);
12092+
12093+ /* Restore the original query, plan, and mod_stmt flag */
12094+ expr -> query = saved_query ;
12095+ expr -> plan = saved_plan ;
12096+ stmt -> mod_stmt = saved_mod_stmt ;
1200212097 }
1200312098 PG_END_TRY ();
1200412099
12005- /* Restore the original query, plan, and mod_stmt flag */
12006- expr -> query = saved_query ;
12007- expr -> plan = saved_plan ;
12008- stmt -> mod_stmt = saved_mod_stmt ;
12009-
1201012100 /* Free the rewritten query string */
1201112101 pfree (rewritten_query .data );
1201212102
0 commit comments