@@ -150,7 +150,8 @@ SPI_connect_ext(int options)
150
150
* XXX It could be better to use PortalContext as the parent context in
151
151
* all cases, but we may not be inside a portal (consider deferred-trigger
152
152
* execution). Perhaps CurTransactionContext could be an option? For now
153
- * it doesn't matter because we clean up explicitly in AtEOSubXact_SPI().
153
+ * it doesn't matter because we clean up explicitly in AtEOSubXact_SPI();
154
+ * but see also AtEOXact_SPI().
154
155
*/
155
156
_SPI_current -> procCxt = AllocSetContextCreate (_SPI_current -> atomic ? TopTransactionContext : PortalContext ,
156
157
"SPI Proc" ,
@@ -208,20 +209,26 @@ SPI_finish(void)
208
209
return SPI_OK_FINISH ;
209
210
}
210
211
212
+ /*
213
+ * SPI_start_transaction is a no-op, kept for backwards compatibility.
214
+ * SPI callers are *always* inside a transaction.
215
+ */
211
216
void
212
217
SPI_start_transaction (void )
213
218
{
214
- MemoryContext oldcontext = CurrentMemoryContext ;
215
-
216
- StartTransactionCommand ();
217
- MemoryContextSwitchTo (oldcontext );
218
219
}
219
220
220
221
static void
221
222
_SPI_commit (bool chain )
222
223
{
223
224
MemoryContext oldcontext = CurrentMemoryContext ;
224
225
226
+ /*
227
+ * Complain if we are in a context that doesn't permit transaction
228
+ * termination. (Note: here and _SPI_rollback should be the only places
229
+ * that throw ERRCODE_INVALID_TRANSACTION_TERMINATION, so that callers can
230
+ * test for that with security that they know what happened.)
231
+ */
225
232
if (_SPI_current -> atomic )
226
233
ereport (ERROR ,
227
234
(errcode (ERRCODE_INVALID_TRANSACTION_TERMINATION ),
@@ -234,40 +241,74 @@ _SPI_commit(bool chain)
234
241
* top-level transaction in such a block violates that idea. A future PL
235
242
* implementation might have different ideas about this, in which case
236
243
* this restriction would have to be refined or the check possibly be
237
- * moved out of SPI into the PLs.
244
+ * moved out of SPI into the PLs. Note however that the code below relies
245
+ * on not being within a subtransaction.
238
246
*/
239
247
if (IsSubTransaction ())
240
248
ereport (ERROR ,
241
249
(errcode (ERRCODE_INVALID_TRANSACTION_TERMINATION ),
242
250
errmsg ("cannot commit while a subtransaction is active" )));
243
251
244
- /*
245
- * Hold any pinned portals that any PLs might be using. We have to do
246
- * this before changing transaction state, since this will run
247
- * user-defined code that might throw an error.
248
- */
249
- HoldPinnedPortals ();
252
+ /* XXX this ain't re-entrant enough for my taste */
253
+ if (chain )
254
+ SaveTransactionCharacteristics ();
250
255
251
- /* Start the actual commit */
252
- _SPI_current -> internal_xact = true;
256
+ /* Catch any error occurring during the COMMIT */
257
+ PG_TRY ();
258
+ {
259
+ /* Protect current SPI stack entry against deletion */
260
+ _SPI_current -> internal_xact = true;
253
261
254
- /* Release snapshots associated with portals */
255
- ForgetPortalSnapshots ();
262
+ /*
263
+ * Hold any pinned portals that any PLs might be using. We have to do
264
+ * this before changing transaction state, since this will run
265
+ * user-defined code that might throw an error.
266
+ */
267
+ HoldPinnedPortals ();
256
268
257
- if ( chain )
258
- SaveTransactionCharacteristics ();
269
+ /* Release snapshots associated with portals */
270
+ ForgetPortalSnapshots ();
259
271
260
- CommitTransactionCommand ();
272
+ /* Do the deed */
273
+ CommitTransactionCommand ();
261
274
262
- if (chain )
263
- {
275
+ /* Immediately start a new transaction */
264
276
StartTransactionCommand ();
265
- RestoreTransactionCharacteristics ();
277
+ if (chain )
278
+ RestoreTransactionCharacteristics ();
279
+
280
+ MemoryContextSwitchTo (oldcontext );
281
+
282
+ _SPI_current -> internal_xact = false;
266
283
}
284
+ PG_CATCH ();
285
+ {
286
+ ErrorData * edata ;
267
287
268
- MemoryContextSwitchTo (oldcontext );
288
+ /* Save error info in caller's context */
289
+ MemoryContextSwitchTo (oldcontext );
290
+ edata = CopyErrorData ();
291
+ FlushErrorState ();
269
292
270
- _SPI_current -> internal_xact = false;
293
+ /*
294
+ * Abort the failed transaction. If this fails too, we'll just
295
+ * propagate the error out ... there's not that much we can do.
296
+ */
297
+ AbortCurrentTransaction ();
298
+
299
+ /* ... and start a new one */
300
+ StartTransactionCommand ();
301
+ if (chain )
302
+ RestoreTransactionCharacteristics ();
303
+
304
+ MemoryContextSwitchTo (oldcontext );
305
+
306
+ _SPI_current -> internal_xact = false;
307
+
308
+ /* Now that we've cleaned up the transaction, re-throw the error */
309
+ ReThrowError (edata );
310
+ }
311
+ PG_END_TRY ();
271
312
}
272
313
273
314
void
@@ -287,6 +328,7 @@ _SPI_rollback(bool chain)
287
328
{
288
329
MemoryContext oldcontext = CurrentMemoryContext ;
289
330
331
+ /* see under SPI_commit() */
290
332
if (_SPI_current -> atomic )
291
333
ereport (ERROR ,
292
334
(errcode (ERRCODE_INVALID_TRANSACTION_TERMINATION ),
@@ -298,34 +340,68 @@ _SPI_rollback(bool chain)
298
340
(errcode (ERRCODE_INVALID_TRANSACTION_TERMINATION ),
299
341
errmsg ("cannot roll back while a subtransaction is active" )));
300
342
301
- /*
302
- * Hold any pinned portals that any PLs might be using. We have to do
303
- * this before changing transaction state, since this will run
304
- * user-defined code that might throw an error, and in any case couldn't
305
- * be run in an already-aborted transaction.
306
- */
307
- HoldPinnedPortals ();
343
+ /* XXX this ain't re-entrant enough for my taste */
344
+ if (chain )
345
+ SaveTransactionCharacteristics ();
308
346
309
- /* Start the actual rollback */
310
- _SPI_current -> internal_xact = true;
347
+ /* Catch any error occurring during the ROLLBACK */
348
+ PG_TRY ();
349
+ {
350
+ /* Protect current SPI stack entry against deletion */
351
+ _SPI_current -> internal_xact = true;
311
352
312
- /* Release snapshots associated with portals */
313
- ForgetPortalSnapshots ();
353
+ /*
354
+ * Hold any pinned portals that any PLs might be using. We have to do
355
+ * this before changing transaction state, since this will run
356
+ * user-defined code that might throw an error, and in any case
357
+ * couldn't be run in an already-aborted transaction.
358
+ */
359
+ HoldPinnedPortals ();
314
360
315
- if ( chain )
316
- SaveTransactionCharacteristics ();
361
+ /* Release snapshots associated with portals */
362
+ ForgetPortalSnapshots ();
317
363
318
- AbortCurrentTransaction ();
364
+ /* Do the deed */
365
+ AbortCurrentTransaction ();
319
366
320
- if (chain )
321
- {
367
+ /* Immediately start a new transaction */
322
368
StartTransactionCommand ();
323
- RestoreTransactionCharacteristics ();
369
+ if (chain )
370
+ RestoreTransactionCharacteristics ();
371
+
372
+ MemoryContextSwitchTo (oldcontext );
373
+
374
+ _SPI_current -> internal_xact = false;
324
375
}
376
+ PG_CATCH ();
377
+ {
378
+ ErrorData * edata ;
325
379
326
- MemoryContextSwitchTo (oldcontext );
380
+ /* Save error info in caller's context */
381
+ MemoryContextSwitchTo (oldcontext );
382
+ edata = CopyErrorData ();
383
+ FlushErrorState ();
327
384
328
- _SPI_current -> internal_xact = false;
385
+ /*
386
+ * Try again to abort the failed transaction. If this fails too,
387
+ * we'll just propagate the error out ... there's not that much we can
388
+ * do.
389
+ */
390
+ AbortCurrentTransaction ();
391
+
392
+ /* ... and start a new one */
393
+ StartTransactionCommand ();
394
+ if (chain )
395
+ RestoreTransactionCharacteristics ();
396
+
397
+ MemoryContextSwitchTo (oldcontext );
398
+
399
+ _SPI_current -> internal_xact = false;
400
+
401
+ /* Now that we've cleaned up the transaction, re-throw the error */
402
+ ReThrowError (edata );
403
+ }
404
+ PG_END_TRY ();
329
405
}
330
406
331
407
void
@@ -340,38 +416,55 @@ SPI_rollback_and_chain(void)
340
416
_SPI_rollback (true);
341
417
}
342
418
343
- /*
344
- * Clean up SPI state. Called on transaction end (of non-SPI-internal
345
- * transactions) and when returning to the main loop on error.
346
- */
347
- void
348
- SPICleanup (void )
349
- {
350
- _SPI_current = NULL ;
351
- _SPI_connected = -1 ;
352
- /* Reset API global variables, too */
353
- SPI_processed = 0 ;
354
- SPI_tuptable = NULL ;
355
- SPI_result = 0 ;
356
- }
357
-
358
419
/*
359
420
* Clean up SPI state at transaction commit or abort.
360
421
*/
361
422
void
362
423
AtEOXact_SPI (bool isCommit )
363
424
{
364
- /* Do nothing if the transaction end was initiated by SPI. */
365
- if (_SPI_current && _SPI_current -> internal_xact )
366
- return ;
425
+ bool found = false;
367
426
368
- if (isCommit && _SPI_connected != -1 )
427
+ /*
428
+ * Pop stack entries, stopping if we find one marked internal_xact (that
429
+ * one belongs to the caller of SPI_commit or SPI_abort).
430
+ */
431
+ while (_SPI_connected >= 0 )
432
+ {
433
+ _SPI_connection * connection = & (_SPI_stack [_SPI_connected ]);
434
+
435
+ if (connection -> internal_xact )
436
+ break ;
437
+
438
+ found = true;
439
+
440
+ /*
441
+ * We need not release the procedure's memory contexts explicitly, as
442
+ * they'll go away automatically when their parent context does; see
443
+ * notes in SPI_connect_ext.
444
+ */
445
+
446
+ /*
447
+ * Restore outer global variables and pop the stack entry. Unlike
448
+ * SPI_finish(), we don't risk switching to memory contexts that might
449
+ * be already gone.
450
+ */
451
+ SPI_processed = connection -> outer_processed ;
452
+ SPI_tuptable = connection -> outer_tuptable ;
453
+ SPI_result = connection -> outer_result ;
454
+
455
+ _SPI_connected -- ;
456
+ if (_SPI_connected < 0 )
457
+ _SPI_current = NULL ;
458
+ else
459
+ _SPI_current = & (_SPI_stack [_SPI_connected ]);
460
+ }
461
+
462
+ /* We should only find entries to pop during an ABORT. */
463
+ if (found && isCommit )
369
464
ereport (WARNING ,
370
465
(errcode (ERRCODE_WARNING ),
371
466
errmsg ("transaction left non-empty SPI stack" ),
372
467
errhint ("Check for missing \"SPI_finish\" calls." )));
373
-
374
- SPICleanup ();
375
468
}
376
469
377
470
/*
0 commit comments