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