Skip to content

Commit 4625bd2

Browse files
committed
Fix INSERT EXEC transaction handling with OUTPUT parameters
- Add transaction control handling for INSERT EXEC context - Block COMMIT/ROLLBACK that would terminate outer INSERT EXEC transaction - Handle BEGIN TRANSACTION/COMMIT inside INSERT EXEC procedures correctly - Fix row count reporting for INSERT EXEC with dynamic SQL - Register is_insert_exec_rewrite_active_hook for engine integration This change ensures SQL Server-compatible transaction behavior during INSERT EXEC operations, where ROLLBACK is blocked and BEGIN/COMMIT are allowed but don't affect the outer INSERT EXEC transaction.
1 parent 95a615f commit 4625bd2

File tree

6 files changed

+208
-24
lines changed

6 files changed

+208
-24
lines changed

contrib/babelfishpg_tds/src/backend/tds/tdsresponse.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2827,6 +2827,30 @@ StatementEnd_Internal(PLtsql_execstate *estate, PLtsql_stmt *stmt, bool error)
28272827
{
28282828
is_proc = true;
28292829
command_type = TDS_CMD_EXECUTE;
2830+
2831+
/*
2832+
* For INSERT EXEC with dynamic SQL (INSERT INTO t EXEC(@var)),
2833+
* we need to send the row count. The insert_exec field is set
2834+
* when this is an INSERT EXEC statement.
2835+
*/
2836+
if (stmt->cmd_type == PLTSQL_STMT_EXEC_BATCH)
2837+
{
2838+
PLtsql_stmt_exec_batch *batch_stmt = (PLtsql_stmt_exec_batch *) stmt;
2839+
if (batch_stmt->insert_exec)
2840+
{
2841+
command_type = TDS_CMD_INSERT;
2842+
row_count_valid = true;
2843+
}
2844+
}
2845+
else if (stmt->cmd_type == PLTSQL_STMT_EXEC)
2846+
{
2847+
PLtsql_stmt_exec *exec_stmt = (PLtsql_stmt_exec *) stmt;
2848+
if (exec_stmt->insert_exec)
2849+
{
2850+
command_type = TDS_CMD_INSERT;
2851+
row_count_valid = true;
2852+
}
2853+
}
28302854
}
28312855
break;
28322856
default:

contrib/babelfishpg_tsql/src/hooks.c

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,41 @@ pltsql_bbfCustomProcessUtility(ParseState *pstate, PlannedStmt *pstmt, const cha
807807
}
808808
case T_TransactionStmt:
809809
{
810-
if (NestedTranCount > 0 || (sql_dialect == SQL_DIALECT_TSQL && !IsTransactionBlockActive()))
810+
TransactionStmt *txn_stmt = (TransactionStmt *) parsetree;
811+
bool is_active = IsTransactionBlockActive();
812+
bool rewrite_active = pltsql_insert_exec_rewrite_active();
813+
bool estate_insert_exec = exec_state_call_stack &&
814+
exec_state_call_stack->estate &&
815+
exec_state_call_stack->estate->insert_exec;
816+
bool in_insert_exec = estate_insert_exec || rewrite_active;
817+
818+
/*
819+
* Block COMMIT/ROLLBACK that would terminate the outer transaction during INSERT EXEC.
820+
* This check must happen BEFORE we decide whether to call PLTsqlProcessTransaction
821+
* or fall through to standard PostgreSQL handling, because when NestedTranCount == 0
822+
* and IsTransactionBlockActive() is true, we would fall through to standard handling
823+
* which would terminate the outer INSERT EXEC transaction.
824+
*
825+
* SQL Server error 3916: Cannot use the COMMIT statement within an INSERT-EXEC
826+
* statement unless BEGIN TRANSACTION is used first.
827+
*
828+
* This means if NestedTranCount == 0 (no BEGIN TRAN), COMMIT should fail.
829+
*/
830+
if (in_insert_exec && txn_stmt->kind == TRANS_STMT_COMMIT && NestedTranCount == 0)
831+
{
832+
ereport(ERROR,
833+
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
834+
errmsg("Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first.")));
835+
}
836+
837+
if (in_insert_exec && txn_stmt->kind == TRANS_STMT_ROLLBACK)
838+
{
839+
ereport(ERROR,
840+
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
841+
errmsg("Cannot use the ROLLBACK statement within an INSERT-EXEC statement.")));
842+
}
843+
844+
if (NestedTranCount > 0 || (sql_dialect == SQL_DIALECT_TSQL && !is_active))
811845
{
812846
PLTsqlProcessTransaction(parsetree, params, qc);
813847
return true;

contrib/babelfishpg_tsql/src/pl_handler.c

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2920,6 +2920,7 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
29202920
Node *tbltypStmt = NULL;
29212921
ListCell *parameter;
29222922
HeapTuple proctup;
2923+
bool xactStarted = IsTransactionOrTransactionBlock();
29232924

29242925
cfs = makeNode(CreateFunctionStmt);
29252926
cfs->returnType = NULL;
@@ -2941,7 +2942,8 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
29412942
/* PG_TRY block is to ensure we call EventTriggerEndCompleteQuery */
29422943
PG_TRY();
29432944
{
2944-
StartTransactionCommand();
2945+
if (!xactStarted)
2946+
StartTransactionCommand();
29452947
if (isCompleteQuery)
29462948
EventTriggerDDLCommandStart(parsetree);
29472949

@@ -3135,7 +3137,8 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
31353137
*/
31363138
alter_bbf_schema_permissions_catalog(stmt->func, cfs->parameters, stmt->objtype);
31373139
}
3138-
CommitTransactionCommand();
3140+
if (!xactStarted)
3141+
CommitTransactionCommand();
31393142
}
31403143
PG_FINALLY();
31413144
{
@@ -3220,6 +3223,7 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
32203223
List *oldColumnAcls = NIL;
32213224
bool isCompleteQuery = (context != PROCESS_UTILITY_SUBCOMMAND);
32223225
bool needCleanup;
3226+
bool xactStarted = IsTransactionOrTransactionBlock();
32233227

32243228
if (!IS_TDS_CLIENT())
32253229
{
@@ -3232,7 +3236,8 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
32323236

32333237
PG_TRY();
32343238
{
3235-
StartTransactionCommand();
3239+
if (!xactStarted)
3240+
StartTransactionCommand();
32363241
pltsql_current_query_is_view_definition = true;
32373242

32383243
/* Without this, DDL event triggers won't fire for ALTER VIEW operations
@@ -3315,7 +3320,8 @@ bbf_ProcessUtility(PlannedStmt *pstmt,
33153320
if(oldViewAcl != NULL)
33163321
pfree(oldViewAcl);
33173322
}
3318-
CommitTransactionCommand();
3323+
if (!xactStarted)
3324+
CommitTransactionCommand();
33193325
}
33203326
PG_FINALLY();
33213327
{

contrib/babelfishpg_tsql/src/pltsql.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2230,7 +2230,7 @@ void PLTsqlProcessTransaction(Node *parsetree,
22302230
ParamListInfo params,
22312231
QueryCompletion *qc);
22322232

2233-
extern void PLTsqlStartTransaction(char *txnName);
2233+
extern void PLTsqlStartTransaction(char *txnName, bool is_explicit);
22342234
extern void PLTsqlCommitTransaction(QueryCompletion *qc, bool chain);
22352235
extern void PLTsqlRollbackTransaction(char *txnName, QueryCompletion *qc, bool chain);
22362236
extern void pltsql_start_txn(void);
@@ -2408,6 +2408,8 @@ extern void pltsql_clear_insert_exec_rewrite_context(void);
24082408
extern bool pltsql_insert_exec_rewrite_active(void);
24092409
extern const char *pltsql_get_insert_exec_target_table(void);
24102410
extern const char *pltsql_get_insert_exec_column_list(void);
2411+
extern int pltsql_get_insert_exec_start_nested_tran_count(void);
2412+
extern void pltsql_update_insert_exec_start_tran_count(void);
24112413
extern bool pltsql_insert_exec_skip_triggers(void);
24122414
extern void pltsql_set_insert_exec_skip_triggers(bool skip);
24132415

contrib/babelfishpg_tsql/src/pltsql_utils.c

Lines changed: 133 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,42 @@ PLTsqlProcessTransaction(Node *parsetree,
120120
{
121121
case TRANS_STMT_BEGIN:
122122
{
123-
PLTsqlStartTransaction(txnName);
123+
PLTsqlStartTransaction(txnName, true); /* explicit BEGIN TRAN */
124124
}
125125
break;
126126

127127
case TRANS_STMT_COMMIT:
128128
{
129-
if (exec_state_call_stack &&
130-
exec_state_call_stack->estate &&
131-
exec_state_call_stack->estate->insert_exec &&
132-
NestedTranCount <= 1)
129+
/*
130+
* Block COMMIT that would terminate the outer transaction during INSERT EXEC.
131+
*
132+
* We check both:
133+
* 1. estate->insert_exec - the traditional tuple store approach
134+
* 2. pltsql_insert_exec_rewrite_active() - the new query rewriting approach
135+
*
136+
* SQL Server error 3916: Cannot use the COMMIT statement within an INSERT-EXEC
137+
* statement unless BEGIN TRANSACTION is used first.
138+
*
139+
* The check compares current NestedTranCount with the value at INSERT EXEC start.
140+
* If they're equal, it means no explicit BEGIN TRAN was issued inside the procedure,
141+
* so COMMIT should fail. If NestedTranCount > start value, there was an explicit
142+
* BEGIN TRAN, so COMMIT is allowed.
143+
*/
144+
bool estate_insert_exec = exec_state_call_stack &&
145+
exec_state_call_stack->estate &&
146+
exec_state_call_stack->estate->insert_exec;
147+
bool rewrite_active = pltsql_insert_exec_rewrite_active();
148+
int start_tran_count = pltsql_get_insert_exec_start_nested_tran_count();
149+
150+
/*
151+
* For rewrite approach: check if NestedTranCount is at or below the start value.
152+
* For traditional approach: check if NestedTranCount == 0.
153+
*/
154+
if (rewrite_active && start_tran_count >= 0 && NestedTranCount <= start_tran_count)
155+
ereport(ERROR,
156+
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
157+
errmsg("Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first.")));
158+
else if (estate_insert_exec && NestedTranCount == 0)
133159
ereport(ERROR,
134160
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
135161
errmsg("Cannot use the COMMIT statement within an INSERT-EXEC statement unless BEGIN TRANSACTION is used first.")));
@@ -140,9 +166,16 @@ PLTsqlProcessTransaction(Node *parsetree,
140166

141167
case TRANS_STMT_ROLLBACK:
142168
{
143-
if (exec_state_call_stack &&
144-
exec_state_call_stack->estate &&
145-
exec_state_call_stack->estate->insert_exec)
169+
/*
170+
* Block ROLLBACK during INSERT EXEC.
171+
* Check both the traditional and query rewriting approaches.
172+
*/
173+
bool in_insert_exec = (exec_state_call_stack &&
174+
exec_state_call_stack->estate &&
175+
exec_state_call_stack->estate->insert_exec) ||
176+
pltsql_insert_exec_rewrite_active();
177+
178+
if (in_insert_exec)
146179
ereport(ERROR,
147180
(errcode(ERRCODE_TRANSACTION_ROLLBACK),
148181
errmsg("Cannot use the ROLLBACK statement within an INSERT-EXEC statement.")));
@@ -784,11 +817,35 @@ pltsql_read_procedure_info(StringInfo inout_str,
784817
}
785818

786819
void
787-
PLTsqlStartTransaction(char *txnName)
820+
PLTsqlStartTransaction(char *txnName, bool is_explicit)
788821
{
789-
elog(DEBUG2, "TSQL TXN Start transaction %d", NestedTranCount);
790822
if (!IsTransactionBlockActive())
791823
{
824+
/*
825+
* When in INSERT EXEC context, we're already inside a transaction
826+
* (the INSERT EXEC's transaction), but IsTransactionBlockActive()
827+
* returns false because we're in SPI context. In this case, we should
828+
* NOT call BeginTransactionBlock() because:
829+
* 1. We're already in a transaction
830+
* 2. BeginTransactionBlock() would put the transaction in TBLOCK_BEGIN
831+
* state, but CommitTransactionCommand() won't be called to transition
832+
* it to TBLOCK_INPROGRESS, causing "unexpected state BEGIN" error
833+
* when COMMIT is later executed.
834+
*
835+
* Instead, we just increment NestedTranCount to track the nested
836+
* transaction level.
837+
*/
838+
if (pltsql_insert_exec_rewrite_active())
839+
{
840+
/* Just increment NestedTranCount, don't start a new transaction block */
841+
++NestedTranCount;
842+
843+
if (*pltsql_protocol_plugin_ptr && (*pltsql_protocol_plugin_ptr)->set_at_at_stat_var)
844+
(*pltsql_protocol_plugin_ptr)->set_at_at_stat_var(TRANCOUNT_TYPE, NestedTranCount, 0);
845+
846+
return;
847+
}
848+
792849
Assert(NestedTranCount == 0);
793850
BeginTransactionBlock();
794851

@@ -803,12 +860,48 @@ PLTsqlStartTransaction(char *txnName)
803860

804861
if (*pltsql_protocol_plugin_ptr && (*pltsql_protocol_plugin_ptr)->set_at_at_stat_var)
805862
(*pltsql_protocol_plugin_ptr)->set_at_at_stat_var(TRANCOUNT_TYPE, NestedTranCount, 0);
863+
864+
/*
865+
* If we're in INSERT EXEC context and this is an implicit transaction
866+
* (not an explicit BEGIN TRAN), update the start_nested_tran_count so that
867+
* the COMMIT check correctly identifies that no explicit BEGIN TRAN was issued.
868+
*
869+
* This is needed because the INSERT EXEC context saves start_nested_tran_count
870+
* before the procedure starts executing, but implicit transactions are
871+
* started inside the procedure by INSERT/UPDATE/DELETE statements.
872+
*/
873+
if (!is_explicit && pltsql_insert_exec_rewrite_active())
874+
{
875+
pltsql_update_insert_exec_start_tran_count();
876+
}
806877
}
807878

808879
void
809880
PLTsqlCommitTransaction(QueryCompletion *qc, bool chain)
810881
{
811-
elog(DEBUG2, "TSQL TXN Commit transaction %d", NestedTranCount);
882+
/*
883+
* During INSERT EXEC, BEGIN TRANSACTION inside the procedure doesn't actually
884+
* start a PostgreSQL transaction block (it just increments NestedTranCount).
885+
* So COMMIT should also just decrement NestedTranCount without calling
886+
* RequireTransactionBlock or EndTransactionBlock.
887+
*
888+
* We detect this case by checking if we're in INSERT EXEC context and
889+
* NestedTranCount is above the start value (meaning there was a BEGIN TRAN
890+
* inside the procedure that we're now committing).
891+
*/
892+
if (pltsql_insert_exec_rewrite_active())
893+
{
894+
int start_tran_count = pltsql_get_insert_exec_start_nested_tran_count();
895+
if (start_tran_count >= 0 && NestedTranCount > start_tran_count)
896+
{
897+
/* Just decrement NestedTranCount, don't call EndTransactionBlock() */
898+
--NestedTranCount;
899+
if (*pltsql_protocol_plugin_ptr && (*pltsql_protocol_plugin_ptr)->set_at_at_stat_var)
900+
(*pltsql_protocol_plugin_ptr)->set_at_at_stat_var(TRANCOUNT_TYPE, NestedTranCount, 0);
901+
return;
902+
}
903+
}
904+
812905
if (NestedTranCount <= 1)
813906
{
814907
RequireTransactionBlock(true, "COMMIT");
@@ -859,16 +952,41 @@ PLTsqlRollbackTransaction(char *txnName, QueryCompletion *qc, bool chain)
859952
void
860953
pltsql_start_txn(void)
861954
{
862-
PLTsqlStartTransaction(NULL);
863-
CommitTransactionCommand();
955+
/*
956+
* During INSERT EXEC, we're already in a transaction context.
957+
* PLTsqlStartTransaction will just increment NestedTranCount without
958+
* calling BeginTransactionBlock(). In this case, we should also skip
959+
* CommitTransactionCommand() to avoid messing up the transaction state.
960+
*/
961+
bool in_insert_exec = pltsql_insert_exec_rewrite_active();
962+
963+
PLTsqlStartTransaction(NULL, false); /* implicit transaction */
964+
965+
if (!in_insert_exec)
966+
{
967+
CommitTransactionCommand();
968+
}
864969
}
865970

866971
void
867972
pltsql_commit_txn(void)
868973
{
974+
/*
975+
* During INSERT EXEC, we're already in a transaction context.
976+
* PLTsqlCommitTransaction will just decrement NestedTranCount without
977+
* calling EndTransactionBlock(). In this case, we should also skip
978+
* CommitTransactionCommand() and StartTransactionCommand() to avoid
979+
* messing up the transaction state.
980+
*/
981+
bool in_insert_exec = pltsql_insert_exec_rewrite_active();
982+
869983
PLTsqlCommitTransaction(NULL, false);
870-
CommitTransactionCommand();
871-
StartTransactionCommand();
984+
985+
if (!in_insert_exec)
986+
{
987+
CommitTransactionCommand();
988+
StartTransactionCommand();
989+
}
872990
}
873991

874992
void

test/JDBC/expected/BABEL-INSERT-EXECUTE.out

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ insert into t3 execute sp_multi_selects;
6767
GO
6868
~~ERROR (Code: 33557097)~~
6969

70-
~~ERROR (Message: structure of query does not match function result type)~~
70+
~~ERROR (Message: Number of given values does not match target table definition)~~
7171

7272
select * from t3;
7373
GO
@@ -456,7 +456,7 @@ insert into t2 execute sp_select_mismatch_with_dml
456456
go
457457
~~ERROR (Code: 33557097)~~
458458

459-
~~ERROR (Message: structure of query does not match function result type)~~
459+
~~ERROR (Message: INSERT has more expressions than target columns)~~
460460

461461
select * from t1;
462462
GO
@@ -513,7 +513,7 @@ insert into t2 execute sp_select_mismatch_after_subtran;
513513
GO
514514
~~ERROR (Code: 33557097)~~
515515

516-
~~ERROR (Message: structure of query does not match function result type)~~
516+
~~ERROR (Message: INSERT has more expressions than target columns)~~
517517

518518
select * from t1;
519519
GO

0 commit comments

Comments
 (0)