@@ -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
786819void
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
808879void
809880PLTsqlCommitTransaction (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)
859952void
860953pltsql_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
866971void
867972pltsql_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
874992void
0 commit comments