Skip to content

Commit 4cb263f

Browse files
committed
Fix errstart error in INSERT EXEC error handling
The 'errstart was not called' panic occurred when INSERT EXEC encountered errors (e.g., column mismatch). The issue was in the error handling flow: 1. When an error occurred, it was pushed onto PostgreSQL's error stack 2. PG_FINALLY called exec_drop_insert_exec_temp_table() to clean up 3. DROP TABLE failed with 'table is being used by active queries' 4. The PG_CATCH in exec_drop_insert_exec_temp_table called FlushErrorState() 5. FlushErrorState() cleared ALL errors, including the original error 6. When CopyErrorData() was called later, the stack was empty -> panic Fix: - Move exec_drop_insert_exec_temp_table() from PG_FINALLY to success path only - In PG_FINALLY, only clean up estate fields without attempting DROP TABLE - In exec_flush_insert_exec_temp_table, use PG_RE_THROW() instead of CopyErrorData/FlushErrorState/ReThrowError to preserve the error stack - Wrap DROP TABLE in a subtransaction to isolate any failures - Temp table is cleaned up when session ends on error paths Also added comments explaining ReThrowError usage in iterative_exec.c
1 parent e49169f commit 4cb263f

File tree

3 files changed

+153
-24
lines changed

3 files changed

+153
-24
lines changed

contrib/babelfishpg_tsql/src/iterative_exec.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ exec_stmt_throw(PLtsql_execstate *estate, PLtsql_stmt_throw *stmt)
189189
estate->cur_error->severity,
190190
estate->cur_error->state,
191191
true /* rethrow */ );
192+
/*
193+
* Use ReThrowError here because the error was previously caught
194+
* and the error stack was flushed. ReThrowError will re-populate
195+
* the error stack with the copied error data and then throw it.
196+
*/
192197
ReThrowError(estate->cur_error->error);
193198
}
194199
else
@@ -1651,6 +1656,12 @@ exec_stmt_iterative(PLtsql_execstate *estate, ExecCodes *exec_codes, ExecConfig_
16511656
if (ignore_catch_block_for_unmapped_error(estate) || terminate_batch)
16521657
{
16531658
elog(DEBUG1, "TSQL TXN Ignore catch block error mapping failed : %d", last_error_mapping_failed);
1659+
/*
1660+
* Use ReThrowError here because restore_ctx_partial1 has already
1661+
* called FlushErrorState(), so the error stack is empty.
1662+
* ReThrowError will re-populate the error stack with the copied
1663+
* error data and then throw it.
1664+
*/
16541665
ReThrowError(estate->cur_error->error);
16551666
}
16561667

contrib/babelfishpg_tsql/src/pl_exec-2.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1347,6 +1347,17 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13471347
SPI_freetuptable(SPI_tuptable);
13481348
exec_flush_insert_exec_temp_table(estate);
13491349
}
1350+
1351+
/*
1352+
* Drop temp table on SUCCESS path only.
1353+
* On error paths, the table might still be in use by active queries,
1354+
* and trying to drop it would fail. The temp table will be cleaned
1355+
* up when the session ends.
1356+
*/
1357+
if (insert_exec_context_set && estate->insert_exec_temp_table != NULL)
1358+
{
1359+
exec_drop_insert_exec_temp_table(estate);
1360+
}
13501361
}
13511362
PG_FINALLY();
13521363
{
@@ -1356,10 +1367,27 @@ exec_stmt_exec(PLtsql_execstate *estate, PLtsql_stmt_exec *stmt)
13561367
pltsql_clear_insert_exec_rewrite_context();
13571368
}
13581369

1359-
/* Drop temp table on any exit (flush already happened on success path) */
1370+
/*
1371+
* On error paths, just clean up the estate fields without trying
1372+
* to drop the temp table. The table might still be in use by
1373+
* active queries, and trying to drop it would fail with an error
1374+
* that would mask the original error.
1375+
*
1376+
* The temp table will be cleaned up when the session ends.
1377+
*/
13601378
if (insert_exec_context_set && estate->insert_exec_temp_table != NULL)
13611379
{
1362-
exec_drop_insert_exec_temp_table(estate);
1380+
/* Free the TopMemoryContext allocations */
1381+
if (estate->insert_exec_temp_table)
1382+
pfree(estate->insert_exec_temp_table);
1383+
if (estate->insert_exec_target_table)
1384+
pfree(estate->insert_exec_target_table);
1385+
if (estate->insert_exec_column_list)
1386+
pfree(estate->insert_exec_column_list);
1387+
estate->insert_exec_temp_table = NULL;
1388+
estate->insert_exec_target_table = NULL;
1389+
estate->insert_exec_column_list = NULL;
1390+
estate->insert_exec_identity_insert = false;
13631391
}
13641392

13651393
/*

contrib/babelfishpg_tsql/src/pl_exec.c

Lines changed: 112 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
1167811732
void
1167911733
exec_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

Comments
 (0)