已合并
【特性】【Opengauss回合】存储过程支持COMMIT/ROLLBACK事务控制 #123
AtomGit-Bot创建于 2020年8月21日
【特性】【Opengauss回合】存储过程支持COMMIT/ROLLBACK事务控制 #123
已合并
共 27 个文件变更+1665-129
| @@ -1105,6 +1105,10 @@ static Datum fmgr_security_definer(PG_FUNCTION_ARGS) | |||
| 1105 | volatile int save_nestlevel; | 1105 | volatile int save_nestlevel; |
| 1106 | PgStat_FunctionCallUsage fcusage; | 1106 | PgStat_FunctionCallUsage fcusage; |
| 1107 | 1107 | ||
| 1108 | + /* Does not allow commit in pre setting scenary */ | ||
| 1109 | + bool savedisTopLevelForSTP = u_sess->SPI_cxt.is_toplevel_stp; | ||
| 1110 | + u_sess->SPI_cxt.is_toplevel_stp = false; | ||
| 1111 | + | ||
| 1108 | if (!fcinfo->flinfo->fn_extra) { | 1112 | if (!fcinfo->flinfo->fn_extra) { |
| 1109 | HeapTuple tuple; | 1113 | HeapTuple tuple; |
| 1110 | Form_pg_proc procedureStruct; | 1114 | Form_pg_proc procedureStruct; |
| @@ -1216,6 +1220,9 @@ static Datum fmgr_security_definer(PG_FUNCTION_ARGS) | |||
| 1216 | (*fmgr_hook)(FHET_END, &(fcache->flinfo), &(fcache->arg)); | 1220 | (*fmgr_hook)(FHET_END, &(fcache->flinfo), &(fcache->arg)); |
| 1217 | } | 1221 | } |
| 1218 | 1222 | ||
| 1223 | + /* restore is_toplevel_stp */ | ||
| 1224 | + u_sess->SPI_cxt.is_toplevel_stp = savedisTopLevelForSTP; | ||
| 1225 | + | ||
| 1219 | return result; | 1226 | return result; |
| 1220 | } | 1227 | } |
| 1221 | 1228 | ||
| @@ -27,6 +27,8 @@ | |||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | 29 | ||
| 30 | + | ||
| 31 | + | ||
| 30 | 32 | ||
| 31 | 33 | ||
| 32 | 34 | ||
| @@ -594,6 +596,36 @@ void PortalHashTableDeleteAll(void) | |||
| 594 | } | 596 | } |
| 595 | } | 597 | } |
| 596 | 598 | ||
| 599 | +/* | ||
| 600 | + * "Hold" a portal. Prepare it for access by later transactions. | ||
| 601 | + */ | ||
| 602 | +static void HoldPortal(Portal portal) | ||
| 603 | +{ | ||
| 604 | + /* | ||
| 605 | + * Note that PersistHoldablePortal() must release all resources | ||
| 606 | + * used by the portal that are local to the creating transaction. | ||
| 607 | + */ | ||
| 608 | + PortalCreateHoldStore(portal); | ||
| 609 | + PersistHoldablePortal(portal); | ||
| 610 | + | ||
| 611 | + /* drop cached plan reference, if any */ | ||
| 612 | + PortalReleaseCachedPlan(portal); | ||
| 613 | + | ||
| 614 | + /* | ||
| 615 | + * Any resources belonging to the portal will be released in the | ||
| 616 | + * upcoming transaction-wide cleanup; the portal will no longer | ||
| 617 | + * have its own resources. | ||
| 618 | + */ | ||
| 619 | + portal->resowner = NULL; | ||
| 620 | + | ||
| 621 | + /* | ||
| 622 | + * Having successfully exported the holdable cursor, mark it as | ||
| 623 | + * not belonging to this transaction. | ||
| 624 | + */ | ||
| 625 | + portal->createSubid = InvalidSubTransactionId; | ||
| 626 | + portal->activeSubid = InvalidSubTransactionId; | ||
| 627 | +} | ||
| 628 | + | ||
| 597 | /* | 629 | /* |
| 598 | * Pre-commit processing for portals. | 630 | * Pre-commit processing for portals. |
| 599 | * | 631 | * |
| @@ -606,7 +638,7 @@ void PortalHashTableDeleteAll(void) | |||
| 606 | * Returns TRUE if any portals changed state (possibly causing user-defined | 638 | * Returns TRUE if any portals changed state (possibly causing user-defined |
| 607 | * code to be run), FALSE if not. | 639 | * code to be run), FALSE if not. |
| 608 | */ | 640 | */ |
| 609 | -bool PreCommit_Portals(bool isPrepare) | 641 | +bool PreCommit_Portals(bool isPrepare, bool stpCommit) |
| 610 | { | 642 | { |
| 611 | bool result = false; | 643 | bool result = false; |
| 612 | HASH_SEQ_STATUS status; | 644 | HASH_SEQ_STATUS status; |
| @@ -619,12 +651,14 @@ bool PreCommit_Portals(bool isPrepare) | |||
| 619 | 651 | ||
| 620 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { | 652 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { |
| 621 | Portal portal = hentry->portal; | 653 | Portal portal = hentry->portal; |
| 654 | + ResourceOwner owner = portal->resowner; | ||
| 622 | 655 | ||
| 623 | /* | 656 | /* |
| 624 | * There should be no pinned portals anymore. Complain if someone | 657 | * There should be no pinned portals anymore. Complain if someone |
| 625 | - * leaked one. | 658 | + * leaked one. Auto-held portals are allowed; we assume that whoever |
| 659 | + * pinned them is managing them. | ||
| 626 | */ | 660 | */ |
| 627 | - if (portal->portalPinned) | 661 | + if (portal->portalPinned && !portal->autoHeld) |
| 628 | ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("cannot commit while a portal is pinned"))); | 662 | ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("cannot commit while a portal is pinned"))); |
| 629 | 663 | ||
| 630 | /* | 664 | /* |
| @@ -635,7 +669,22 @@ bool PreCommit_Portals(bool isPrepare) | |||
| 635 | * still going to go away, so don't leave a dangling pointer. | 669 | * still going to go away, so don't leave a dangling pointer. |
| 636 | */ | 670 | */ |
| 637 | if (portal->status == PORTAL_ACTIVE) { | 671 | if (portal->status == PORTAL_ACTIVE) { |
| 638 | - portal->resowner = NULL; | 672 | + /* |
| 673 | + * If we are in multi commit and we also have owner, then need to cleanup all the snapshots | ||
| 674 | + * during commit time. Otherwise it will cause leak snapshots reference warning. | ||
| 675 | + */ | ||
| 676 | + if (owner && stpCommit) { | ||
| 677 | + ResourceOwnerDecrementNsnapshots(owner, portal->queryDesc); | ||
| 678 | + } | ||
| 679 | + | ||
| 680 | + /* | ||
| 681 | + * If we are in commit within stored procedure need to keep resowner and it will be used to | ||
| 682 | + * connect new local resources. Otherwise it will cause leak snapshots reference warning, because | ||
| 683 | + * the new snapshot does not have owner. | ||
| 684 | + */ | ||
| 685 | + if (!stpCommit) { | ||
| 686 | + portal->resowner = NULL; | ||
| 687 | + } | ||
| 639 | continue; | 688 | continue; |
| 640 | } | 689 | } |
| 641 | 690 | ||
| @@ -655,28 +704,7 @@ bool PreCommit_Portals(bool isPrepare) | |||
| 655 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 704 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| 656 | errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD"))); | 705 | errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD"))); |
| 657 | 706 | ||
| 658 | - /* | 707 | + HoldPortal(portal); |
| 659 | - * Note that PersistHoldablePortal() must release all resources | ||
| 660 | - * used by the portal that are local to the creating transaction. | ||
| 661 | - */ | ||
| 662 | - PortalCreateHoldStore(portal); | ||
| 663 | - PersistHoldablePortal(portal); | ||
| 664 | - | ||
| 665 | - /* drop cached plan reference, if any */ | ||
| 666 | - PortalReleaseCachedPlan(portal); | ||
| 667 | - | ||
| 668 | - /* | ||
| 669 | - * Any resources belonging to the portal will be released in the | ||
| 670 | - * upcoming transaction-wide cleanup; the portal will no longer | ||
| 671 | - * have its own resources. | ||
| 672 | - */ | ||
| 673 | - portal->resowner = NULL; | ||
| 674 | - | ||
| 675 | - /* | ||
| 676 | - * Having successfully exported the holdable cursor, mark it as | ||
| 677 | - * not belonging to this transaction. | ||
| 678 | - */ | ||
| 679 | - portal->createSubid = InvalidSubTransactionId; | ||
| 680 | 708 | ||
| 681 | /* Report we changed state */ | 709 | /* Report we changed state */ |
| 682 | result = true; | 710 | result = true; |
| @@ -709,13 +737,13 @@ bool PreCommit_Portals(bool isPrepare) | |||
| 709 | /* | 737 | /* |
| 710 | * Abort processing for portals. | 738 | * Abort processing for portals. |
| 711 | * | 739 | * |
| 712 | - * At this point we reset "active" status and run the cleanup hook if | 740 | + * At this point we run the cleanup hook if present, but we can't release the |
| 713 | - * present, but we can't release the portal's memory until the cleanup call. | 741 | + * portal's memory until the cleanup call. |
| 714 | * | 742 | * |
| 715 | * The reason we need to reset active is so that we can replace the unnamed | 743 | * The reason we need to reset active is so that we can replace the unnamed |
| 716 | * portal, else we'll fail to execute ROLLBACK when it arrives. | 744 | * portal, else we'll fail to execute ROLLBACK when it arrives. |
| 717 | */ | 745 | */ |
| 718 | -void AtAbort_Portals(void) | 746 | +void AtAbort_Portals(bool stpRollback) |
| 719 | { | 747 | { |
| 720 | HASH_SEQ_STATUS status; | 748 | HASH_SEQ_STATUS status; |
| 721 | PortalHashEnt* hentry = NULL; | 749 | PortalHashEnt* hentry = NULL; |
| @@ -728,16 +756,28 @@ void AtAbort_Portals(void) | |||
| 728 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { | 756 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { |
| 729 | Portal portal = hentry->portal; | 757 | Portal portal = hentry->portal; |
| 730 | 758 | ||
| 731 | - /* Any portal that was actually running has to be considered broken */ | ||
| 732 | - if (portal->status == PORTAL_ACTIVE) | ||
| 733 | - MarkPortalFailed(portal); | ||
| 734 | - | ||
| 735 | /* | 759 | /* |
| 736 | * Do nothing else to cursors held over from a previous transaction. | 760 | * Do nothing else to cursors held over from a previous transaction. |
| 737 | */ | 761 | */ |
| 738 | if (portal->createSubid == InvalidSubTransactionId) | 762 | if (portal->createSubid == InvalidSubTransactionId) |
| 739 | continue; | 763 | continue; |
| 740 | 764 | ||
| 765 | + /* | ||
| 766 | + * Do nothing to auto-held cursors. This is similar to the case of a | ||
| 767 | + * cursor from a previous transaction, but it could also be that the | ||
| 768 | + * cursor was auto-held in this trasnaction, so it wants to live on. | ||
| 769 | + */ | ||
| 770 | + if (portal->autoHeld) | ||
| 771 | + continue; | ||
| 772 | + | ||
| 773 | + /* | ||
| 774 | + * Within multi rollback need to clean up snapshots before release | ||
| 775 | + * its resource owner. | ||
| 776 | + */ | ||
| 777 | + if (portal->resowner && stpRollback) { | ||
| 778 | + ResourceOwnerDecrementNsnapshots(portal->resowner, portal->queryDesc); | ||
| 779 | + } | ||
| 780 | + | ||
| 741 | /* | 781 | /* |
| 742 | * If it was created in the current transaction, we can't do normal | 782 | * If it was created in the current transaction, we can't do normal |
| 743 | * shutdown on a READY portal either; it might refer to objects | 783 | * shutdown on a READY portal either; it might refer to objects |
| @@ -749,9 +789,10 @@ void AtAbort_Portals(void) | |||
| 749 | 789 | ||
| 750 | /* | 790 | /* |
| 751 | * Allow portalcmds.c to clean up the state it knows about, if we | 791 | * Allow portalcmds.c to clean up the state it knows about, if we |
| 752 | - * haven't already. | 792 | + * haven't already. Should haven't already. Should not clean up portal |
| 793 | + * during multi commit and rollback. | ||
| 753 | */ | 794 | */ |
| 754 | - if (PointerIsValid(portal->cleanup)) { | 795 | + if (PointerIsValid(portal->cleanup) && !stpRollback) { |
| 755 | (*portal->cleanup)(portal); | 796 | (*portal->cleanup)(portal); |
| 756 | portal->cleanup = NULL; | 797 | portal->cleanup = NULL; |
| 757 | } | 798 | } |
| @@ -762,17 +803,24 @@ void AtAbort_Portals(void) | |||
| 762 | /* | 803 | /* |
| 763 | * Any resources belonging to the portal will be released in the | 804 | * Any resources belonging to the portal will be released in the |
| 764 | * upcoming transaction-wide cleanup; they will be gone before we run | 805 | * upcoming transaction-wide cleanup; they will be gone before we run |
| 765 | - * PortalDrop. | 806 | + * PortalDrop. Can not reset resowner NULL if |
| 807 | + * we are in stpRollback, because the Portal is still alive. If we set | ||
| 808 | + * resowner to NULL it will cause leak snapshots reference error, because | ||
| 809 | + * the new snaphosts does not have owner. | ||
| 766 | */ | 810 | */ |
| 767 | - portal->resowner = NULL; | 811 | + if (!stpRollback) { |
| 812 | + portal->resowner = NULL; | ||
| 813 | + } | ||
| 768 | 814 | ||
| 769 | /* | 815 | /* |
| 770 | * Although we can't delete the portal data structure proper, we can | 816 | * Although we can't delete the portal data structure proper, we can |
| 771 | * release any memory in subsidiary contexts, such as executor state. | 817 | * release any memory in subsidiary contexts, such as executor state. |
| 772 | * The cleanup hook was the last thing that might have needed data | 818 | * The cleanup hook was the last thing that might have needed data |
| 773 | - * there. | 819 | + * there. But leave active portals alone. |
| 774 | */ | 820 | */ |
| 775 | - MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); | 821 | + if (portal->status != PORTAL_ACTIVE) { |
| 822 | + MemoryContextDeleteChildren(PortalGetHeapMemory(portal)); | ||
| 823 | + } | ||
| 776 | } | 824 | } |
| 777 | } | 825 | } |
| 778 | 826 | ||
| @@ -793,8 +841,18 @@ void AtCleanup_Portals(void) | |||
| 793 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { | 841 | while ((hentry = (PortalHashEnt*)hash_seq_search(&status)) != NULL) { |
| 794 | Portal portal = hentry->portal; | 842 | Portal portal = hentry->portal; |
| 795 | 843 | ||
| 796 | - /* Do nothing to cursors held over from a previous transaction */ | 844 | + /* |
| 797 | - if (portal->createSubid == InvalidSubTransactionId) { | 845 | + * Do not touch active portals --- this can only happen in the case of |
| 846 | + * a multi-transaction command. | ||
| 847 | + */ | ||
| 848 | + if (portal->status == PORTAL_ACTIVE) | ||
| 849 | + continue; | ||
| 850 | + | ||
| 851 | + /* | ||
| 852 | + * Do nothing to cursors held over from a previous transaction or | ||
| 853 | + * auto-held ones. | ||
| 854 | + */ | ||
| 855 | + if (portal->createSubid == InvalidSubTransactionId || portal->autoHeld) { | ||
| 798 | Assert(portal->status != PORTAL_ACTIVE); | 856 | Assert(portal->status != PORTAL_ACTIVE); |
| 799 | Assert(portal->resowner == NULL); | 857 | Assert(portal->resowner == NULL); |
| 800 | continue; | 858 | continue; |
| @@ -820,6 +878,29 @@ void AtCleanup_Portals(void) | |||
| 820 | } | 878 | } |
| 821 | } | 879 | } |
| 822 | 880 | ||
| 881 | +/* | ||
| 882 | + * Potal-related cleanup when we return to the main loop on error. | ||
| 883 | + * | ||
| 884 | + * This is different from the cleanup at transaction abort. Auto-held portals | ||
| 885 | + * are cleaned up on error but not on transaction abort. | ||
| 886 | + */ | ||
| 887 | +void PortalErrorCleanup(void) | ||
| 888 | +{ | ||
| 889 | + HASH_SEQ_STATUS status; | ||
| 890 | + PortalHashEnt* hentry = NULL; | ||
| 891 | + | ||
| 892 | + hash_seq_init(&status, u_sess->exec_cxt.PortalHashTable); | ||
| 893 | + | ||
| 894 | + while ((hentry = (PortalHashEnt *)hash_seq_search(&status)) != NULL) { | ||
| 895 | + Portal portal = hentry->portal; | ||
| 896 | + | ||
| 897 | + if (portal->autoHeld) { | ||
| 898 | + portal->portalPinned = false; | ||
| 899 | + PortalDrop(portal, false); | ||
| 900 | + } | ||
| 901 | + } | ||
| 902 | +} | ||
| 903 | + | ||
| 823 | /* | 904 | /* |
| 824 | * Pre-subcommit processing for portals. | 905 | * Pre-subcommit processing for portals. |
| 825 | * | 906 | * |
| @@ -1132,3 +1213,55 @@ void ResetPortalCursor(SubTransactionId mySubid, Oid funOid, int funUseCount) | |||
| 1132 | ResetCursorOption(portal, true); | 1213 | ResetCursorOption(portal, true); |
| 1133 | } | 1214 | } |
| 1134 | } | 1215 | } |
| 1216 | + | ||
| 1217 | +/* | ||
| 1218 | + * Hold all pinned portals. | ||
| 1219 | + * | ||
| 1220 | + * When initialing a COMMIT or ROLLBACK insise a procedure, this must be | ||
| 1221 | + * called to protect internally-generated cursors from being dropped during | ||
| 1222 | + * the transaction shutdown. Currently, SPI calls this automatically; PLs | ||
| 1223 | + * that initiate COMMIT or ROLLBACK some other way are on the hook to do it | ||
| 1224 | + * themselves. (Note that we couldn't do this in, say, AtAbort_Portals | ||
| 1225 | + * because we need to run user-defined code while persisting a portal. | ||
| 1226 | + * It's too late to do that once transaction abort has started.) | ||
| 1227 | + * | ||
| 1228 | + * We protect such portals by converting them to held cursors. We mark them | ||
| 1229 | + * as "auto-held" so that exception exit knowns to clean them up. (In normal, | ||
| 1230 | + * non-exception code paths, the PL needs to clean such portals itself, since | ||
| 1231 | + * transaction end won't do it anymore; but that should be normal practice | ||
| 1232 | + * anyway.) | ||
| 1233 | + */ | ||
| 1234 | +void HoldPinnedPortals(void) | ||
| 1235 | +{ | ||
| 1236 | + HASH_SEQ_STATUS status; | ||
| 1237 | + PortalHashEnt* hentry = NULL; | ||
| 1238 | + | ||
| 1239 | + hash_seq_init(&status, u_sess->exec_cxt.PortalHashTable); | ||
| 1240 | + | ||
| 1241 | + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) { | ||
| 1242 | + Portal portal = hentry->portal; | ||
| 1243 | + | ||
| 1244 | + if (portal->portalPinned && !portal->autoHeld) { | ||
| 1245 | + /* | ||
| 1246 | + * Doing transaction control, especially abort, inside a cursor | ||
| 1247 | + * loop that is not read-only, for example using UPDATE | ||
| 1248 | + * ... RETURNING, has weird semantics issues. Also, this | ||
| 1249 | + * implementation wouldn't work, because such portals cannot be | ||
| 1250 | + * held. (The core grammer enforces that only SELECT statements | ||
| 1251 | + * can drive a cursor, but for example PL/pgSQL does not restrict | ||
| 1252 | + * it.) | ||
| 1253 | + */ | ||
| 1254 | + if (portal->strategy != PORTAL_ONE_SELECT) | ||
| 1255 | + ereport(ERROR, | ||
| 1256 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 1257 | + errmsg("cannot perform transaction commands inside a cursor loop that is not read-only"))); | ||
| 1258 | + | ||
| 1259 | + /* Verify it's in a suitable state to be held */ | ||
| 1260 | + if (portal->status != PORTAL_READY) | ||
| 1261 | + elog(ERROR, "pinned portal is not ready to be auto-held"); | ||
| 1262 | + | ||
| 1263 | + HoldPortal(portal); | ||
| 1264 | + portal->autoHeld = true; | ||
| 1265 | + } | ||
| 1266 | + } | ||
| 1267 | +} | ||
| @@ -431,6 +431,10 @@ void ResourceOwnerDelete(ResourceOwner owner) | |||
| 431 | */ | 431 | */ |
| 432 | ResourceOwnerNewParent(owner, NULL); | 432 | ResourceOwnerNewParent(owner, NULL); |
| 433 | 433 | ||
| 434 | + if (owner == t_thrd.utils_cxt.StpSavedResourceOwner) { | ||
| 435 | + return; | ||
| 436 | + } | ||
| 437 | + | ||
| 434 | /* And free the object. */ | 438 | /* And free the object. */ |
| 435 | if (owner->buffers) | 439 | if (owner->buffers) |
| 436 | pfree(owner->buffers); | 440 | pfree(owner->buffers); |
| @@ -469,6 +473,30 @@ ResourceOwner ResourceOwnerGetParent(ResourceOwner owner) | |||
| 469 | return owner->parent; | 473 | return owner->parent; |
| 470 | } | 474 | } |
| 471 | 475 | ||
| 476 | +/* | ||
| 477 | + * Fetch nextchild of a ResourceOwner (returns) | ||
| 478 | + */ | ||
| 479 | +ResourceOwner ResourceOwnerGetNextChild(ResourceOwner owner) | ||
| 480 | +{ | ||
| 481 | + return owner->nextchild; | ||
| 482 | +} | ||
| 483 | + | ||
| 484 | +/* | ||
| 485 | + * Fetch name of a ResourceOwner (should always has value) | ||
| 486 | + */ | ||
| 487 | +const char* ResourceOwnerGetName(ResourceOwner owner) | ||
| 488 | +{ | ||
| 489 | + return owner->name; | ||
| 490 | +} | ||
| 491 | + | ||
| 492 | +/* | ||
| 493 | + * Fetch firstchild of a ResourceOwner. | ||
| 494 | + */ | ||
| 495 | +ResourceOwner ResourceOwnerGetFirstChild(ResourceOwner owner) | ||
| 496 | +{ | ||
| 497 | + return owner->firstchild; | ||
| 498 | +} | ||
| 499 | + | ||
| 472 | /* | 500 | /* |
| 473 | * Reassign a ResourceOwner to have a new parent | 501 | * Reassign a ResourceOwner to have a new parent |
| 474 | */ | 502 | */ |
| @@ -1320,6 +1348,55 @@ void ResourceOwnerForgetSnapshot(ResourceOwner owner, const Snapshot snapshot) | |||
| 1320 | errmsg("snapshot is not owned by resource owner %s", owner->name))); | 1348 | errmsg("snapshot is not owned by resource owner %s", owner->name))); |
| 1321 | } | 1349 | } |
| 1322 | 1350 | ||
| 1351 | +/* | ||
| 1352 | + * This function is used to clean up the snapshots. | ||
| 1353 | + * It will be called by PreCommit_Portals and Abort_Portals. | ||
| 1354 | + */ | ||
| 1355 | +void ResourceOwnerDecrementNsnapshots(ResourceOwner owner, void* queryDesc) | ||
| 1356 | +{ | ||
| 1357 | + QueryDesc* queryDescTemp = (QueryDesc*)queryDesc; | ||
| 1358 | + | ||
| 1359 | + while (owner->nsnapshots > 0) { | ||
| 1360 | + if (queryDescTemp) { | ||
| 1361 | + /* | ||
| 1362 | + * check if owner's snapshot is same as queryDesc's snapshot, need to set queryDesc | ||
| 1363 | + * snapshot to null, because this function will clean up those snapshots. | ||
| 1364 | + */ | ||
| 1365 | + if (owner->snapshots[owner->nsnapshots - 1] == queryDescTemp->estate->es_snapshot) { | ||
| 1366 | + queryDescTemp->estate->es_snapshot = NULL; | ||
| 1367 | + } | ||
| 1368 | + | ||
| 1369 | + if (owner->snapshots[owner->nsnapshots - 1] == queryDescTemp->estate->es_crosscheck_snapshot) { | ||
| 1370 | + queryDescTemp->estate->es_crosscheck_snapshot = NULL; | ||
| 1371 | + } | ||
| 1372 | + | ||
| 1373 | + if (owner->snapshots[owner->nsnapshots - 1] == queryDescTemp->snapshot) { | ||
| 1374 | + queryDescTemp->snapshot = NULL; | ||
| 1375 | + } | ||
| 1376 | + | ||
| 1377 | + if (owner->snapshots[owner->nsnapshots - 1] == queryDescTemp->crosscheck_snapshot) { | ||
| 1378 | + queryDescTemp->crosscheck_snapshot = NULL; | ||
| 1379 | + } | ||
| 1380 | + } | ||
| 1381 | + | ||
| 1382 | + UnregisterSnapshotFromOwner(owner->snapshots[owner->nsnapshots - 1], owner); | ||
| 1383 | + } | ||
| 1384 | +} | ||
| 1385 | + | ||
| 1386 | +/* | ||
| 1387 | + * This function is used to clean up the cached plan. | ||
| 1388 | + * It will ba called by CommitTransaction. | ||
| 1389 | + */ | ||
| 1390 | +void ResourceOwnerDecrementNPlanRefs(ResourceOwner owner, bool useResOwner) | ||
| 1391 | +{ | ||
| 1392 | + if (!owner) { | ||
| 1393 | + return; | ||
| 1394 | + } | ||
| 1395 | + | ||
| 1396 | + while (owner->nplanrefs > 0) { | ||
| 1397 | + ReleaseCachedPlan(owner->planrefs[owner->nplanrefs - 1], useResOwner); | ||
| 1398 | + } | ||
| 1399 | +} | ||
| 1323 | /* | 1400 | /* |
| 1324 | * Debugging subroutine | 1401 | * Debugging subroutine |
| 1325 | */ | 1402 | */ |
| @@ -534,6 +534,14 @@ void UpdateActiveSnapshotCommandId(void) | |||
| 534 | */ | 534 | */ |
| 535 | void PopActiveSnapshot(void) | 535 | void PopActiveSnapshot(void) |
| 536 | { | 536 | { |
| 537 | + /* | ||
| 538 | + * In multi commit/rollback within stored procedure, the ActiveSnapshot already poped. | ||
| 539 | + * Therefore, no need to pop the active snapshot. Otherwise it will cause seg fault. | ||
| 540 | + */ | ||
| 541 | + if (!u_sess->utils_cxt.ActiveSnapshot) { | ||
| 542 | + return; | ||
| 543 | + } | ||
| 544 | + | ||
| 537 | ActiveSnapshotElt* newstack = NULL; | 545 | ActiveSnapshotElt* newstack = NULL; |
| 538 | 546 | ||
| 539 | newstack = u_sess->utils_cxt.ActiveSnapshot->as_next; | 547 | newstack = u_sess->utils_cxt.ActiveSnapshot->as_next; |
| @@ -130,6 +130,8 @@ static bool is_anonymous_block(const char* query); | |||
| 130 | static int exec_stmt_dynfors(PLpgSQL_execstate* estate, PLpgSQL_stmt_dynfors* stmt); | 130 | static int exec_stmt_dynfors(PLpgSQL_execstate* estate, PLpgSQL_stmt_dynfors* stmt); |
| 131 | 131 | ||
| 132 | static void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi); | 132 | static void plpgsql_estate_setup(PLpgSQL_execstate* estate, PLpgSQL_function* func, ReturnSetInfo* rsi); |
| 133 | +static int exec_stmt_commit(PLpgSQL_execstate* estate, PLpgSQL_stmt_commit* stmt); | ||
| 134 | +static int exec_stmt_rollback(PLpgSQL_execstate* estate, PLpgSQL_stmt_rollback* stmt); | ||
| 133 | static void exec_eval_cleanup(PLpgSQL_execstate* estate); | 135 | static void exec_eval_cleanup(PLpgSQL_execstate* estate); |
| 134 | 136 | ||
| 135 | static void exec_prepare_plan(PLpgSQL_execstate* estate, PLpgSQL_expr* expr, int cursorOptions); | 137 | static void exec_prepare_plan(PLpgSQL_execstate* estate, PLpgSQL_expr* expr, int cursorOptions); |
| @@ -291,6 +293,7 @@ Datum plpgsql_exec_function(PLpgSQL_function* func, FunctionCallInfo fcinfo, boo | |||
| 291 | { | 293 | { |
| 292 | PLpgSQL_execstate estate; | 294 | PLpgSQL_execstate estate; |
| 293 | ErrorContextCallback plerrcontext; | 295 | ErrorContextCallback plerrcontext; |
| 296 | + bool savedIsStp; | ||
| 294 | int i; | 297 | int i; |
| 295 | int rc; | 298 | int rc; |
| 296 | 299 | ||
| @@ -412,7 +415,9 @@ Datum plpgsql_exec_function(PLpgSQL_function* func, FunctionCallInfo fcinfo, boo | |||
| 412 | */ | 415 | */ |
| 413 | estate.err_text = NULL; | 416 | estate.err_text = NULL; |
| 414 | estate.err_stmt = (PLpgSQL_stmt*)(func->action); | 417 | estate.err_stmt = (PLpgSQL_stmt*)(func->action); |
| 418 | + savedIsStp = u_sess->SPI_cxt.is_stp; | ||
| 415 | rc = exec_stmt_block(&estate, func->action); | 419 | rc = exec_stmt_block(&estate, func->action); |
| 420 | + u_sess->SPI_cxt.is_stp = savedIsStp; | ||
| 416 | if (rc != PLPGSQL_RC_RETURN) { | 421 | if (rc != PLPGSQL_RC_RETURN) { |
| 417 | estate.err_stmt = NULL; | 422 | estate.err_stmt = NULL; |
| 418 | estate.err_text = NULL; | 423 | estate.err_text = NULL; |
| @@ -633,6 +638,7 @@ HeapTuple plpgsql_exec_trigger(PLpgSQL_function* func, TriggerData* trigdata) | |||
| 633 | PLpgSQL_rec *rec_new = NULL; | 638 | PLpgSQL_rec *rec_new = NULL; |
| 634 | PLpgSQL_rec *rec_old = NULL; | 639 | PLpgSQL_rec *rec_old = NULL; |
| 635 | HeapTuple rettup; | 640 | HeapTuple rettup; |
| 641 | + bool saveIsStp; | ||
| 636 | 642 | ||
| 637 | /* | 643 | /* |
| 638 | * Setup the execution state | 644 | * Setup the execution state |
| @@ -842,7 +848,10 @@ HeapTuple plpgsql_exec_trigger(PLpgSQL_function* func, TriggerData* trigdata) | |||
| 842 | */ | 848 | */ |
| 843 | estate.err_text = NULL; | 849 | estate.err_text = NULL; |
| 844 | estate.err_stmt = (PLpgSQL_stmt*)(func->action); | 850 | estate.err_stmt = (PLpgSQL_stmt*)(func->action); |
| 851 | + saveIsStp = u_sess->SPI_cxt.is_stp; | ||
| 852 | + u_sess->SPI_cxt.is_stp = false; | ||
| 845 | rc = exec_stmt_block(&estate, func->action); | 853 | rc = exec_stmt_block(&estate, func->action); |
| 854 | + u_sess->SPI_cxt.is_stp = saveIsStp; | ||
| 846 | if (rc != PLPGSQL_RC_RETURN) { | 855 | if (rc != PLPGSQL_RC_RETURN) { |
| 847 | estate.err_stmt = NULL; | 856 | estate.err_stmt = NULL; |
| 848 | estate.err_text = NULL; | 857 | estate.err_text = NULL; |
| @@ -1399,6 +1408,10 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) | |||
| 1399 | int i; | 1408 | int i; |
| 1400 | int n; | 1409 | int n; |
| 1401 | SubTransactionId subXid = InvalidSubTransactionId; | 1410 | SubTransactionId subXid = InvalidSubTransactionId; |
| 1411 | + bool savedIsTopLevelForStp = u_sess->SPI_cxt.is_toplevel_stp; | ||
| 1412 | + bool savedIsStp = u_sess->SPI_cxt.is_stp; | ||
| 1413 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 1414 | + | ||
| 1402 | /* | 1415 | /* |
| 1403 | * First initialize all variables declared in this block | 1416 | * First initialize all variables declared in this block |
| 1404 | */ | 1417 | */ |
| @@ -1477,13 +1490,17 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) | |||
| 1477 | } | 1490 | } |
| 1478 | 1491 | ||
| 1479 | if (block->exceptions != NULL) { | 1492 | if (block->exceptions != NULL) { |
| 1493 | + u_sess->SPI_cxt.portal_stp_exception_counter++; | ||
| 1494 | + | ||
| 1480 | /* | 1495 | /* |
| 1481 | * Execute the statements in the block's body inside a sub-transaction | 1496 | * Execute the statements in the block's body inside a sub-transaction |
| 1482 | */ | 1497 | */ |
| 1483 | MemoryContext oldcontext = CurrentMemoryContext; | 1498 | MemoryContext oldcontext = CurrentMemoryContext; |
| 1484 | ResourceOwner oldowner = t_thrd.utils_cxt.CurrentResourceOwner; | 1499 | ResourceOwner oldowner = t_thrd.utils_cxt.CurrentResourceOwner; |
| 1485 | - ExprContext* old_eval_econtext = estate->eval_econtext; | ||
| 1486 | ErrorData* save_cur_error = estate->cur_error; | 1500 | ErrorData* save_cur_error = estate->cur_error; |
| 1501 | + if (!RecoveryInProgress()) { | ||
| 1502 | + oldTransactionId = GetTopTransactionId(); | ||
| 1503 | + } | ||
| 1487 | 1504 | ||
| 1488 | estate->err_text = gettext_noop("during statement block entry"); | 1505 | estate->err_text = gettext_noop("during statement block entry"); |
| 1489 | 1506 | ||
| @@ -1539,13 +1556,25 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) | |||
| 1539 | } | 1556 | } |
| 1540 | 1557 | ||
| 1541 | MemoryContextSwitchTo(oldcontext); | 1558 | MemoryContextSwitchTo(oldcontext); |
| 1559 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 1560 | + if (ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner)) { | ||
| 1561 | + oldowner = ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1562 | + } else { | ||
| 1563 | + if (ResourceOwnerGetParent(t_thrd.utils_cxt.CurrentResourceOwner)) { | ||
| 1564 | + oldowner = ResourceOwnerGetParent(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1565 | + } else { | ||
| 1566 | + oldowner = ResourceOwnerGetFirstChild(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1567 | + } | ||
| 1568 | + } | ||
| 1569 | + } | ||
| 1542 | t_thrd.utils_cxt.CurrentResourceOwner = oldowner; | 1570 | t_thrd.utils_cxt.CurrentResourceOwner = oldowner; |
| 1571 | + u_sess->SPI_cxt.portal_stp_exception_counter--; | ||
| 1543 | 1572 | ||
| 1544 | /* | 1573 | /* |
| 1545 | * Revert to outer eval_econtext. (The inner one was | 1574 | * Revert to outer eval_econtext. (The inner one was |
| 1546 | * automatically cleaned up during subxact exit.) | 1575 | * automatically cleaned up during subxact exit.) |
| 1547 | */ | 1576 | */ |
| 1548 | - estate->eval_econtext = old_eval_econtext; | 1577 | + estate->eval_econtext = u_sess->plsql_cxt.simple_econtext_stack->stack_econtext; |
| 1549 | 1578 | ||
| 1550 | /* | 1579 | /* |
| 1551 | * AtEOSubXact_SPI() should not have popped any SPI context, but | 1580 | * AtEOSubXact_SPI() should not have popped any SPI context, but |
| @@ -1557,6 +1586,10 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) | |||
| 1557 | { | 1586 | { |
| 1558 | ErrorData* edata = NULL; | 1587 | ErrorData* edata = NULL; |
| 1559 | ListCell* e = NULL; | 1588 | ListCell* e = NULL; |
| 1589 | + | ||
| 1590 | + u_sess->SPI_cxt.is_toplevel_stp = savedIsTopLevelForStp; | ||
| 1591 | + u_sess->SPI_cxt.is_stp = savedIsStp; | ||
| 1592 | + | ||
| 1560 | estate->cursor_return_data = saved_cursor_data; | 1593 | estate->cursor_return_data = saved_cursor_data; |
| 1561 | 1594 | ||
| 1562 | /* gs_signal_handle maybe block sigusr2 when accept SIGINT */ | 1595 | /* gs_signal_handle maybe block sigusr2 when accept SIGINT */ |
| @@ -1601,10 +1634,22 @@ static int exec_stmt_block(PLpgSQL_execstate* estate, PLpgSQL_stmt_block* block) | |||
| 1601 | } | 1634 | } |
| 1602 | 1635 | ||
| 1603 | MemoryContextSwitchTo(oldcontext); | 1636 | MemoryContextSwitchTo(oldcontext); |
| 1637 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 1638 | + if (ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner)) { | ||
| 1639 | + oldowner = ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1640 | + } else { | ||
| 1641 | + if (ResourceOwnerGetParent(t_thrd.utils_cxt.CurrentResourceOwner)) { | ||
| 1642 | + oldowner = ResourceOwnerGetParent(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1643 | + } else { | ||
| 1644 | + oldowner = ResourceOwnerGetFirstChild(t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 1645 | + } | ||
| 1646 | + } | ||
| 1647 | + } | ||
| 1604 | t_thrd.utils_cxt.CurrentResourceOwner = oldowner; | 1648 | t_thrd.utils_cxt.CurrentResourceOwner = oldowner; |
| 1605 | 1649 | ||
| 1606 | /* Revert to outer eval_econtext */ | 1650 | /* Revert to outer eval_econtext */ |
| 1607 | - estate->eval_econtext = old_eval_econtext; | 1651 | + estate->eval_econtext = u_sess->plsql_cxt.simple_econtext_stack->stack_econtext; |
| 1652 | + u_sess->SPI_cxt.portal_stp_exception_counter--; | ||
| 1608 | 1653 | ||
| 1609 | /* | 1654 | /* |
| 1610 | * If AtEOSubXact_SPI() popped any SPI context of the subxact, it | 1655 | * If AtEOSubXact_SPI() popped any SPI context of the subxact, it |
| @@ -2061,14 +2106,28 @@ static int exec_stmt_assign(PLpgSQL_execstate* estate, PLpgSQL_stmt_assign* stmt | |||
| 2061 | static int exec_stmt_perform(PLpgSQL_execstate* estate, PLpgSQL_stmt_perform* stmt) | 2106 | static int exec_stmt_perform(PLpgSQL_execstate* estate, PLpgSQL_stmt_perform* stmt) |
| 2062 | { | 2107 | { |
| 2063 | PLpgSQL_expr* expr = stmt->expr; | 2108 | PLpgSQL_expr* expr = stmt->expr; |
| 2109 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 2064 | int rc; | 2110 | int rc; |
| 2065 | 2111 | ||
| 2112 | + if (!RecoveryInProgress()) { | ||
| 2113 | + oldTransactionId = GetTopTransactionId(); | ||
| 2114 | + } | ||
| 2115 | + | ||
| 2066 | rc = exec_run_select(estate, expr, 0, NULL); | 2116 | rc = exec_run_select(estate, expr, 0, NULL); |
| 2067 | if (rc != SPI_OK_SELECT) { | 2117 | if (rc != SPI_OK_SELECT) { |
| 2068 | ereport(DEBUG1, | 2118 | ereport(DEBUG1, |
| 2069 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmodule(MOD_PLSQL), errmsg("exec_run_select returns %d", rc))); | 2119 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmodule(MOD_PLSQL), errmsg("exec_run_select returns %d", rc))); |
| 2070 | } | 2120 | } |
| 2071 | 2121 | ||
| 2122 | + /* | ||
| 2123 | + * This is used for nested STP. If the transaction Id changed, | ||
| 2124 | + * then need to create new econtext for the TopTransaction. | ||
| 2125 | + */ | ||
| 2126 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 2127 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 2128 | + plpgsql_create_econtext(estate); | ||
| 2129 | + } | ||
| 2130 | + | ||
| 2072 | exec_set_found(estate, (estate->eval_processed != 0)); | 2131 | exec_set_found(estate, (estate->eval_processed != 0)); |
| 2073 | exec_eval_cleanup(estate); | 2132 | exec_eval_cleanup(estate); |
| 2074 | 2133 | ||
| @@ -3695,7 +3754,7 @@ static void exec_eval_cleanup(PLpgSQL_execstate* estate) | |||
| 3695 | estate->eval_tuptable = NULL; | 3754 | estate->eval_tuptable = NULL; |
| 3696 | 3755 | ||
| 3697 | /* Clear result of exec_eval_simple_expr (but keep the econtext) */ | 3756 | /* Clear result of exec_eval_simple_expr (but keep the econtext) */ |
| 3698 | - if (estate->eval_econtext != NULL) { | 3757 | + if (estate->eval_econtext != NULL && estate->eval_econtext->ecxt_per_tuple_memory != NULL) { |
| 3699 | ResetExprContext(estate->eval_econtext); | 3758 | ResetExprContext(estate->eval_econtext); |
| 3700 | } | 3759 | } |
| 3701 | } | 3760 | } |
| @@ -3762,6 +3821,11 @@ static int exec_stmt_execsql(PLpgSQL_execstate* estate, PLpgSQL_stmt_execsql* st | |||
| 3762 | int rc; | 3821 | int rc; |
| 3763 | PLpgSQL_expr* expr = stmt->sqlstmt; | 3822 | PLpgSQL_expr* expr = stmt->sqlstmt; |
| 3764 | Cursor_Data* saved_cursor_data = NULL; | 3823 | Cursor_Data* saved_cursor_data = NULL; |
| 3824 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 3825 | + | ||
| 3826 | + if (!RecoveryInProgress()) { | ||
| 3827 | + oldTransactionId = GetTopTransactionId(); | ||
| 3828 | + } | ||
| 3765 | 3829 | ||
| 3766 | /* | 3830 | /* |
| 3767 | * On the first call for this statement generate the plan, and detect | 3831 | * On the first call for this statement generate the plan, and detect |
| @@ -3836,6 +3900,16 @@ static int exec_stmt_execsql(PLpgSQL_execstate* estate, PLpgSQL_stmt_execsql* st | |||
| 3836 | * Execute the plan | 3900 | * Execute the plan |
| 3837 | */ | 3901 | */ |
| 3838 | rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI, estate->readonly_func, tcount); | 3902 | rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI, estate->readonly_func, tcount); |
| 3903 | + | ||
| 3904 | + /* | ||
| 3905 | + * This is used for nested STP. If the transaction Id changed, | ||
| 3906 | + * then need to create new econtext for the TopTransaction. | ||
| 3907 | + */ | ||
| 3908 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 3909 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 3910 | + plpgsql_create_econtext(estate); | ||
| 3911 | + } | ||
| 3912 | + | ||
| 3839 | plpgsql_estate = NULL; | 3913 | plpgsql_estate = NULL; |
| 3840 | 3914 | ||
| 3841 | /* | 3915 | /* |
| @@ -4179,6 +4253,11 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate* estate, PLpgSQL_stmt_dynexecu | |||
| 4179 | FmgrInfo flinfo; | 4253 | FmgrInfo flinfo; |
| 4180 | int ppdindex = 0; | 4254 | int ppdindex = 0; |
| 4181 | int datumindex = 0; | 4255 | int datumindex = 0; |
| 4256 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 4257 | + | ||
| 4258 | + if (!RecoveryInProgress()) { | ||
| 4259 | + oldTransactionId = GetTopTransactionId(); | ||
| 4260 | + } | ||
| 4182 | 4261 | ||
| 4183 | /* Compile the anonymous code block */ | 4262 | /* Compile the anonymous code block */ |
| 4184 | /* support pass external parameter in anonymous block */ | 4263 | /* support pass external parameter in anonymous block */ |
| @@ -4207,6 +4286,16 @@ static int exec_stmt_dynexecute(PLpgSQL_execstate* estate, PLpgSQL_stmt_dynexecu | |||
| 4207 | flinfo.fn_mcxt = CurrentMemoryContext; | 4286 | flinfo.fn_mcxt = CurrentMemoryContext; |
| 4208 | 4287 | ||
| 4209 | (void)plpgsql_exec_function(func, &fake_fcinfo, true); | 4288 | (void)plpgsql_exec_function(func, &fake_fcinfo, true); |
| 4289 | + | ||
| 4290 | + /* | ||
| 4291 | + * This is used for nested STP. If the transaction Id changed, | ||
| 4292 | + * then need to create new econtext for the TopTransaction. | ||
| 4293 | + */ | ||
| 4294 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 4295 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 4296 | + plpgsql_create_econtext(estate); | ||
| 4297 | + } | ||
| 4298 | + | ||
| 4210 | exec_set_sql_isopen(estate, false); | 4299 | exec_set_sql_isopen(estate, false); |
| 4211 | exec_set_sql_cursor_found(estate, PLPGSQL_TRUE); | 4300 | exec_set_sql_cursor_found(estate, PLPGSQL_TRUE); |
| 4212 | exec_set_sql_notfound(estate, PLPGSQL_FALSE); | 4301 | exec_set_sql_notfound(estate, PLPGSQL_FALSE); |
| @@ -4895,6 +4984,58 @@ static int exec_stmt_null(PLpgSQL_execstate* estate, PLpgSQL_stmt* stmt) | |||
| 4895 | */ | 4984 | */ |
| 4896 | static int exec_stmt_commit(PLpgSQL_execstate* estate, PLpgSQL_stmt_commit* stmt) | 4985 | static int exec_stmt_commit(PLpgSQL_execstate* estate, PLpgSQL_stmt_commit* stmt) |
| 4897 | { | 4986 | { |
| 4987 | + const char* PORTAL = "Portal"; | ||
| 4988 | + int subTransactionCount = u_sess->SPI_cxt.portal_stp_exception_counter; | ||
| 4989 | + | ||
| 4990 | + if (u_sess->SPI_cxt.portal_stp_exception_counter == 0) { | ||
| 4991 | + t_thrd.utils_cxt.StpSavedResourceOwner = t_thrd.utils_cxt.CurrentResourceOwner; | ||
| 4992 | + } | ||
| 4993 | + | ||
| 4994 | + if (strcmp(PORTAL, ResourceOwnerGetName(t_thrd.utils_cxt.CurrentResourceOwner)) == 0) { | ||
| 4995 | + if (ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner) | ||
| 4996 | + && (strcmp(PORTAL, ResourceOwnerGetName(ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner))) == 0)) | ||
| 4997 | + ereport(ERROR, | ||
| 4998 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | ||
| 4999 | + errmsg("commit with PE is not supported"))); | ||
| 5000 | + } | ||
| 5001 | + | ||
| 5002 | + SPI_commit(); | ||
| 5003 | + SPI_start_transaction(); | ||
| 5004 | + | ||
| 5005 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 5006 | + plpgsql_create_econtext(estate); | ||
| 5007 | + | ||
| 5008 | + /* link portal to new TopTransaction */ | ||
| 5009 | + ResourceOwnerNewParent(t_thrd.utils_cxt.StpSavedResourceOwner, t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 5010 | + t_thrd.utils_cxt.CurrentResourceOwner = t_thrd.utils_cxt.StpSavedResourceOwner; | ||
| 5011 | + | ||
| 5012 | + while (subTransactionCount > 0) { | ||
| 5013 | + if (u_sess->SPI_cxt.portal_stp_exception_counter > 0) { | ||
| 5014 | + MemoryContext oldcontext = CurrentMemoryContext; | ||
| 5015 | + | ||
| 5016 | + estate->err_text = gettext_noop("during statement block entry"); | ||
| 5017 | + | ||
| 5018 | + /* CN should send savesopint command to remote nodes to begin sub transaction remotely */ | ||
| 5019 | + if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) { | ||
| 5020 | + pgxc_node_remote_savepoint("Savepoint s1", EXEC_ON_ALL_NODES, true, true); | ||
| 5021 | + } | ||
| 5022 | + | ||
| 5023 | + BeginInternalSubTransaction(NULL); | ||
| 5024 | + | ||
| 5025 | + /* Want to run statements inside function's memory context */ | ||
| 5026 | + MemoryContextSwitchTo(oldcontext); | ||
| 5027 | + | ||
| 5028 | + plpgsql_create_econtext(estate); | ||
| 5029 | + estate->err_text = NULL; | ||
| 5030 | + } | ||
| 5031 | + | ||
| 5032 | + subTransactionCount--; | ||
| 5033 | + } | ||
| 5034 | + | ||
| 5035 | + if (u_sess->SPI_cxt.portal_stp_exception_counter == 0) { | ||
| 5036 | + t_thrd.utils_cxt.CurrentResourceOwner = t_thrd.utils_cxt.StpSavedResourceOwner; | ||
| 5037 | + } | ||
| 5038 | + | ||
| 4898 | return PLPGSQL_RC_OK; | 5039 | return PLPGSQL_RC_OK; |
| 4899 | } | 5040 | } |
| 4900 | 5041 | ||
| @@ -4905,6 +5046,58 @@ static int exec_stmt_commit(PLpgSQL_execstate* estate, PLpgSQL_stmt_commit* stmt | |||
| 4905 | */ | 5046 | */ |
| 4906 | static int exec_stmt_rollback(PLpgSQL_execstate* estate, PLpgSQL_stmt_rollback* stmt) | 5047 | static int exec_stmt_rollback(PLpgSQL_execstate* estate, PLpgSQL_stmt_rollback* stmt) |
| 4907 | { | 5048 | { |
| 5049 | + const char* PORTAL = "Portal"; | ||
| 5050 | + int subTransactionCount = u_sess->SPI_cxt.portal_stp_exception_counter; | ||
| 5051 | + | ||
| 5052 | + if (u_sess->SPI_cxt.portal_stp_exception_counter == 0) { | ||
| 5053 | + t_thrd.utils_cxt.StpSavedResourceOwner = t_thrd.utils_cxt.CurrentResourceOwner; | ||
| 5054 | + } | ||
| 5055 | + | ||
| 5056 | + if (strcmp(PORTAL, ResourceOwnerGetName(t_thrd.utils_cxt.CurrentResourceOwner)) == 0) { | ||
| 5057 | + if (ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner) | ||
| 5058 | + && (strcmp(PORTAL, ResourceOwnerGetName(ResourceOwnerGetNextChild(t_thrd.utils_cxt.CurrentResourceOwner))) == 0)) | ||
| 5059 | + ereport(ERROR, | ||
| 5060 | + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | ||
| 5061 | + errmsg("commit with PE is not supported"))); | ||
| 5062 | + } | ||
| 5063 | + | ||
| 5064 | + SPI_rollback(); | ||
| 5065 | + SPI_start_transaction(); | ||
| 5066 | + | ||
| 5067 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 5068 | + plpgsql_create_econtext(estate); | ||
| 5069 | + | ||
| 5070 | + /* link portal to new TopTransaction */ | ||
| 5071 | + ResourceOwnerNewParent(t_thrd.utils_cxt.StpSavedResourceOwner, t_thrd.utils_cxt.CurrentResourceOwner); | ||
| 5072 | + t_thrd.utils_cxt.CurrentResourceOwner = t_thrd.utils_cxt.StpSavedResourceOwner; | ||
| 5073 | + | ||
| 5074 | + while (subTransactionCount > 0) { | ||
| 5075 | + if (u_sess->SPI_cxt.portal_stp_exception_counter > 0) { | ||
| 5076 | + MemoryContext oldcontext = CurrentMemoryContext; | ||
| 5077 | + | ||
| 5078 | + estate->err_text = gettext_noop("during statement block entry"); | ||
| 5079 | + | ||
| 5080 | + /* CN should send savesopint command to remote nodes to begin sub transaction remotely */ | ||
| 5081 | + if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) { | ||
| 5082 | + pgxc_node_remote_savepoint("Savepoint s1", EXEC_ON_ALL_NODES, true, true); | ||
| 5083 | + } | ||
| 5084 | + | ||
| 5085 | + BeginInternalSubTransaction(NULL); | ||
| 5086 | + | ||
| 5087 | + /* Want to run statements inside function's memory context */ | ||
| 5088 | + MemoryContextSwitchTo(oldcontext); | ||
| 5089 | + | ||
| 5090 | + plpgsql_create_econtext(estate); | ||
| 5091 | + estate->err_text = NULL; | ||
| 5092 | + } | ||
| 5093 | + | ||
| 5094 | + subTransactionCount--; | ||
| 5095 | + } | ||
| 5096 | + | ||
| 5097 | + if (u_sess->SPI_cxt.portal_stp_exception_counter == 0) { | ||
| 5098 | + t_thrd.utils_cxt.CurrentResourceOwner = t_thrd.utils_cxt.StpSavedResourceOwner; | ||
| 5099 | + } | ||
| 5100 | + | ||
| 4908 | return PLPGSQL_RC_OK; | 5101 | return PLPGSQL_RC_OK; |
| 4909 | } | 5102 | } |
| 4910 | 5103 | ||
| @@ -7555,26 +7748,21 @@ static void plpgsql_destroy_econtext(PLpgSQL_execstate* estate) | |||
| 7555 | */ | 7748 | */ |
| 7556 | void plpgsql_xact_cb(XactEvent event, void* arg) | 7749 | void plpgsql_xact_cb(XactEvent event, void* arg) |
| 7557 | { | 7750 | { |
| 7751 | + u_sess->plsql_cxt.simple_eval_estate = NULL; | ||
| 7752 | + | ||
| 7558 | /* | 7753 | /* |
| 7559 | * If we are doing a clean transaction shutdown, free the EState (so that | 7754 | * If we are doing a clean transaction shutdown, free the EState (so that |
| 7560 | * any remaining resources will be released correctly). In an abort, we | 7755 | * any remaining resources will be released correctly). In an abort, we |
| 7561 | * expect the regular abort recovery procedures to release everything of | 7756 | * expect the regular abort recovery procedures to release everything of |
| 7562 | * interest. | 7757 | * interest. |
| 7563 | */ | 7758 | */ |
| 7564 | - if (event == XACT_EVENT_PREROLLBACK_CLEANUP) { | 7759 | + u_sess->plsql_cxt.simple_econtext_stack = NULL; |
| 7565 | - return; | 7760 | + if (event != XACT_EVENT_ABORT) { |
| 7566 | - } else if (event != XACT_EVENT_ABORT) { | ||
| 7567 | - /* Shouldn't be any econtext stack entries left at commit */ | ||
| 7568 | - AssertEreport(u_sess->plsql_cxt.simple_econtext_stack == NULL, | ||
| 7569 | - MOD_PLSQL, | ||
| 7570 | - "Shouldn't be any econtext stack entries left at commit"); | ||
| 7571 | - | ||
| 7572 | if (u_sess->plsql_cxt.simple_eval_estate) { | 7761 | if (u_sess->plsql_cxt.simple_eval_estate) { |
| 7573 | FreeExecutorState(u_sess->plsql_cxt.simple_eval_estate); | 7762 | FreeExecutorState(u_sess->plsql_cxt.simple_eval_estate); |
| 7574 | } | 7763 | } |
| 7575 | u_sess->plsql_cxt.simple_eval_estate = NULL; | 7764 | u_sess->plsql_cxt.simple_eval_estate = NULL; |
| 7576 | } else { | 7765 | } else { |
| 7577 | - u_sess->plsql_cxt.simple_econtext_stack = NULL; | ||
| 7578 | u_sess->plsql_cxt.simple_eval_estate = NULL; | 7766 | u_sess->plsql_cxt.simple_eval_estate = NULL; |
| 7579 | } | 7767 | } |
| 7580 | } | 7768 | } |
| @@ -179,6 +179,7 @@ Datum plpgsql_call_handler(PG_FUNCTION_ARGS) | |||
| 179 | // PGSTAT_INIT_PLSQL_TIME_RECORD | 179 | // PGSTAT_INIT_PLSQL_TIME_RECORD |
| 180 | int64 startTime = 0; | 180 | int64 startTime = 0; |
| 181 | bool needRecord = false; | 181 | bool needRecord = false; |
| 182 | + bool nonatomic = false; | ||
| 182 | 183 | ||
| 183 | bool outer_is_stream = false; | 184 | bool outer_is_stream = false; |
| 184 | bool outer_is_stream_support = false; | 185 | bool outer_is_stream_support = false; |
| @@ -193,11 +194,20 @@ Datum plpgsql_call_handler(PG_FUNCTION_ARGS) | |||
| 193 | } | 194 | } |
| 194 | 195 | ||
| 195 | 196 | ||
| 197 | + /* | ||
| 198 | + * If the atomic stored in fcinfo is false means allow | ||
| 199 | + * commit/rollback within stord procedure. | ||
| 200 | + * set the noatomic and will be reused within function. | ||
| 201 | + */ | ||
| 202 | + nonatomic = fcinfo->context && | ||
| 203 | + IsA(fcinfo->context, FunctionScanState) && | ||
| 204 | + !castNode(FunctionScanState, fcinfo->context)->atomic; | ||
| 205 | + | ||
| 196 | _PG_init(); | 206 | _PG_init(); |
| 197 | /* | 207 | /* |
| 198 | * Connect to SPI manager | 208 | * Connect to SPI manager |
| 199 | */ | 209 | */ |
| 200 | - if ((rc = SPI_connect()) != SPI_OK_CONNECT) { | 210 | + if ((rc = SPI_connect_ext(DestSPI, NULL, NULL, nonatomic ? SPI_OPT_NOATOMIC : 0)) != SPI_OK_CONNECT) { |
| 201 | ereport(ERROR, | 211 | ereport(ERROR, |
| 202 | (errmodule(MOD_PLSQL), | 212 | (errmodule(MOD_PLSQL), |
| 203 | errcode(ERRCODE_UNDEFINED_OBJECT), | 213 | errcode(ERRCODE_UNDEFINED_OBJECT), |
| @@ -338,7 +348,7 @@ Datum plpgsql_inline_handler(PG_FUNCTION_ARGS) | |||
| 338 | /* | 348 | /* |
| 339 | * Connect to SPI manager | 349 | * Connect to SPI manager |
| 340 | */ | 350 | */ |
| 341 | - if ((rc = SPI_connect()) != SPI_OK_CONNECT) { | 351 | + if ((rc = SPI_connect_ext(DestSPI, NULL, NULL, codeblock->atomic ? 0 : SPI_OPT_NOATOMIC)) != SPI_OK_CONNECT) { |
| 342 | ereport(ERROR, | 352 | ereport(ERROR, |
| 343 | (errmodule(MOD_PLSQL), | 353 | (errmodule(MOD_PLSQL), |
| 344 | errcode(ERRCODE_SPI_CONNECTION_FAILURE), | 354 | errcode(ERRCODE_SPI_CONNECTION_FAILURE), |
| @@ -2133,7 +2133,7 @@ Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid) | |||
| 2133 | * ExecuteDoStmt | 2133 | * ExecuteDoStmt |
| 2134 | * Execute inline procedural-language code | 2134 | * Execute inline procedural-language code |
| 2135 | */ | 2135 | */ |
| 2136 | -void ExecuteDoStmt(const DoStmt* stmt) | 2136 | +void ExecuteDoStmt(const DoStmt* stmt, bool atomic) |
| 2137 | { | 2137 | { |
| 2138 | InlineCodeBlock* codeblock = makeNode(InlineCodeBlock); | 2138 | InlineCodeBlock* codeblock = makeNode(InlineCodeBlock); |
| 2139 | ListCell* arg = NULL; | 2139 | ListCell* arg = NULL; |
| @@ -2200,6 +2200,7 @@ void ExecuteDoStmt(const DoStmt* stmt) | |||
| 2200 | 2200 | ||
| 2201 | /* get the handler function's OID */ | 2201 | /* get the handler function's OID */ |
| 2202 | laninline = languageStruct->laninline; | 2202 | laninline = languageStruct->laninline; |
| 2203 | + codeblock->atomic = atomic; | ||
| 2203 | if (!OidIsValid(laninline)) | 2204 | if (!OidIsValid(laninline)) |
| 2204 | ereport(ERROR, | 2205 | ereport(ERROR, |
| 2205 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 2206 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| @@ -44,6 +44,7 @@ | |||
| 44 | 44 | ||
| 45 | 45 | ||
| 46 | 46 | ||
| 47 | + | ||
| 47 | 48 | ||
| 48 | 49 | ||
| 49 | 50 | ||
| @@ -93,6 +94,7 @@ | |||
| 93 | 94 | ||
| 94 | 95 | ||
| 95 | 96 | ||
| 97 | + | ||
| 96 | 98 | ||
| 97 | 99 | ||
| 98 | 100 | ||
| @@ -2039,6 +2041,7 @@ static void exec_simple_query(const char* query_string, MessageType messageType, | |||
| 2039 | * significant to PreventTransactionChain.) | 2041 | * significant to PreventTransactionChain.) |
| 2040 | */ | 2042 | */ |
| 2041 | isTopLevel = (list_length(parsetree_list) == 1); | 2043 | isTopLevel = (list_length(parsetree_list) == 1); |
| 2044 | + u_sess->SPI_cxt.is_toplevel_stp = isTopLevel; | ||
| 2042 | 2045 | ||
| 2043 | if (isTopLevel != 1) | 2046 | if (isTopLevel != 1) |
| 2044 | t_thrd.explain_cxt.explain_perf_mode = EXPLAIN_NORMAL; | 2047 | t_thrd.explain_cxt.explain_perf_mode = EXPLAIN_NORMAL; |
| @@ -2478,6 +2481,12 @@ static void exec_simple_query(const char* query_string, MessageType messageType, | |||
| 2478 | 2481 | ||
| 2479 | MemoryContextDelete(OptimizerContext); | 2482 | MemoryContextDelete(OptimizerContext); |
| 2480 | 2483 | ||
| 2484 | + /* Reset store procedure's session variables. */ | ||
| 2485 | + u_sess->SPI_cxt.is_toplevel_stp = false; | ||
| 2486 | + u_sess->SPI_cxt.is_stp = true; | ||
| 2487 | + u_sess->SPI_cxt.is_proconfig_set = false; | ||
| 2488 | + u_sess->SPI_cxt.portal_stp_exception_counter = 0; | ||
| 2489 | + | ||
| 2481 | /* | 2490 | /* |
| 2482 | * Close down transaction statement, if one is open. | 2491 | * Close down transaction statement, if one is open. |
| 2483 | */ | 2492 | */ |
| @@ -4331,6 +4340,7 @@ static void exec_execute_message(const char* portal_name, long max_rows) | |||
| 4331 | bool execute_is_fetch = false; | 4340 | bool execute_is_fetch = false; |
| 4332 | bool was_logged = false; | 4341 | bool was_logged = false; |
| 4333 | char msec_str[32]; | 4342 | char msec_str[32]; |
| 4343 | + bool savedIsTopLevelForSTP = false; | ||
| 4334 | 4344 | ||
| 4335 | gstrace_entry(GS_TRC_ID_exec_execute_message); | 4345 | gstrace_entry(GS_TRC_ID_exec_execute_message); |
| 4336 | /* Adjust destination to tell printtup.c what to do */ | 4346 | /* Adjust destination to tell printtup.c what to do */ |
| @@ -4469,6 +4479,9 @@ static void exec_execute_message(const char* portal_name, long max_rows) | |||
| 4469 | /* Check for cancel signal before we start execution */ | 4479 | /* Check for cancel signal before we start execution */ |
| 4470 | CHECK_FOR_INTERRUPTS(); | 4480 | CHECK_FOR_INTERRUPTS(); |
| 4471 | 4481 | ||
| 4482 | + savedIsTopLevelForSTP = u_sess->SPI_cxt.is_toplevel_stp; | ||
| 4483 | + u_sess->SPI_cxt.is_toplevel_stp = true; | ||
| 4484 | + | ||
| 4472 | /* | 4485 | /* |
| 4473 | * Okay to run the portal. | 4486 | * Okay to run the portal. |
| 4474 | */ | 4487 | */ |
| @@ -4482,6 +4495,7 @@ static void exec_execute_message(const char* portal_name, long max_rows) | |||
| 4482 | receiver, | 4495 | receiver, |
| 4483 | completionTag); | 4496 | completionTag); |
| 4484 | 4497 | ||
| 4498 | + u_sess->SPI_cxt.is_toplevel_stp = savedIsTopLevelForSTP; | ||
| 4485 | (*receiver->rDestroy)(receiver); | 4499 | (*receiver->rDestroy)(receiver); |
| 4486 | 4500 | ||
| 4487 | if (completed) { | 4501 | if (completed) { |
| @@ -7371,6 +7385,10 @@ int PostgresMain(int argc, char* argv[], const char* dbname, const char* usernam | |||
| 7371 | if (sigsetjmp(local_sigjmp_buf, 1) != 0) { | 7385 | if (sigsetjmp(local_sigjmp_buf, 1) != 0) { |
| 7372 | gstrace_tryblock_exit(true, oldTryCounter); | 7386 | gstrace_tryblock_exit(true, oldTryCounter); |
| 7373 | 7387 | ||
| 7388 | + u_sess->SPI_cxt.is_stp = true; | ||
| 7389 | + u_sess->SPI_cxt.is_proconfig_set = false; | ||
| 7390 | + u_sess->SPI_cxt.portal_stp_exception_counter = 0; | ||
| 7391 | + | ||
| 7374 | (void)pgstat_report_waitstatus(STATE_WAIT_UNDEFINED); | 7392 | (void)pgstat_report_waitstatus(STATE_WAIT_UNDEFINED); |
| 7375 | t_thrd.pgxc_cxt.GlobalNetInstr = NULL; | 7393 | t_thrd.pgxc_cxt.GlobalNetInstr = NULL; |
| 7376 | /* output the memory tracking information when error happened */ | 7394 | /* output the memory tracking information when error happened */ |
| @@ -7494,6 +7512,9 @@ int PostgresMain(int argc, char* argv[], const char* dbname, const char* usernam | |||
| 7494 | */ | 7512 | */ |
| 7495 | AbortCurrentTransaction(); | 7513 | AbortCurrentTransaction(); |
| 7496 | 7514 | ||
| 7515 | + PortalErrorCleanup(); | ||
| 7516 | + SPICleanup(); | ||
| 7517 | + | ||
| 7497 | /* Notice: at the most time it isn't necessary to call because | 7518 | /* Notice: at the most time it isn't necessary to call because |
| 7498 | * all the LWLocks are released in AbortCurrentTransaction(). | 7519 | * all the LWLocks are released in AbortCurrentTransaction(). |
| 7499 | * but in some rare exception not in one transaction (for | 7520 | * but in some rare exception not in one transaction (for |
| @@ -4425,7 +4425,7 @@ void standard_ProcessUtility(Node* parse_tree, const char* query_string, ParamLi | |||
| 4425 | break; | 4425 | break; |
| 4426 | 4426 | ||
| 4427 | case T_DoStmt: | 4427 | case T_DoStmt: |
| 4428 | - ExecuteDoStmt((DoStmt*)parse_tree); | 4428 | + ExecuteDoStmt((DoStmt*)parse_tree, (!u_sess->SPI_cxt.is_toplevel_stp || IsTransactionBlock())); |
| 4429 | break; | 4429 | break; |
| 4430 | 4430 | ||
| 4431 | case T_CreatedbStmt: | 4431 | case T_CreatedbStmt: |
| @@ -239,6 +239,10 @@ static void knl_u_SPI_init(knl_u_SPI_context* spi) | |||
| 239 | spi->_curid = -1; | 239 | spi->_curid = -1; |
| 240 | spi->_stack = NULL; | 240 | spi->_stack = NULL; |
| 241 | spi->_current = NULL; | 241 | spi->_current = NULL; |
| 242 | + spi->is_toplevel_stp = false; | ||
| 243 | + spi->is_stp = true; | ||
| 244 | + spi->is_proconfig_set = false; | ||
| 245 | + spi->portal_stp_exception_counter = 0; | ||
| 242 | } | 246 | } |
| 243 | 247 | ||
| 244 | static void knl_u_trigger_init(knl_u_trigger_context* tri_cxt) | 248 | static void knl_u_trigger_init(knl_u_trigger_context* tri_cxt) |
| @@ -845,6 +845,7 @@ static void knl_t_utils_init(knl_t_utils_context* utils_cxt) | |||
| 845 | utils_cxt->CurrentResourceOwner = NULL; | 845 | utils_cxt->CurrentResourceOwner = NULL; |
| 846 | utils_cxt->CurTransactionResourceOwner = NULL; | 846 | utils_cxt->CurTransactionResourceOwner = NULL; |
| 847 | utils_cxt->TopTransactionResourceOwner = NULL; | 847 | utils_cxt->TopTransactionResourceOwner = NULL; |
| 848 | + utils_cxt->StpSavedResourceOwner = NULL; | ||
| 848 | utils_cxt->ResourceRelease_callbacks = NULL; | 849 | utils_cxt->ResourceRelease_callbacks = NULL; |
| 849 | utils_cxt->SortColumnOptimize = false; | 850 | utils_cxt->SortColumnOptimize = false; |
| 850 | utils_cxt->pRelatedRel = NULL; | 851 | utils_cxt->pRelatedRel = NULL; |
| @@ -2042,6 +2042,52 @@ static Datum ExecMakeFunctionResultNoSets( | |||
| 2042 | PgStat_FunctionCallUsage fcusage; | 2042 | PgStat_FunctionCallUsage fcusage; |
| 2043 | int i; | 2043 | int i; |
| 2044 | int* var_dno = NULL; | 2044 | int* var_dno = NULL; |
| 2045 | + FunctionScanState* node = NULL; | ||
| 2046 | + HeapTuple tup; | ||
| 2047 | + FuncExpr* fexpr = NULL; | ||
| 2048 | + bool savedIsStp = u_sess->SPI_cxt.is_stp; | ||
| 2049 | + bool savedProConfigIsSet = u_sess->SPI_cxt.is_proconfig_set; | ||
| 2050 | + bool proIsProcedure = false; | ||
| 2051 | + bool supportTransaction = false; | ||
| 2052 | + | ||
| 2053 | + | ||
| 2054 | + if (IS_PGXC_COORDINATOR) { | ||
| 2055 | + supportTransaction = true; | ||
| 2056 | + } | ||
| 2057 | + | ||
| 2058 | + supportTransaction = true; | ||
| 2059 | + | ||
| 2060 | + | ||
| 2061 | + if (supportTransaction && IsA(fcache->xprstate.expr, FuncExpr)) { | ||
| 2062 | + fexpr = (FuncExpr*)(fcache->xprstate.expr); | ||
| 2063 | + node = makeNode(FunctionScanState); | ||
| 2064 | + node->atomic = (!u_sess->SPI_cxt.is_toplevel_stp || IsTransactionBlock()); | ||
| 2065 | + | ||
| 2066 | + /* | ||
| 2067 | + * If proconfig is set we can't allow transaction commands because of the | ||
| 2068 | + * way the GUC stacking works. The transaction boundary would have to pop | ||
| 2069 | + * the proconfig setting off the stack. That restriction could be lefted | ||
| 2070 | + * by redesigning the GUC nesting mechanism a bit. | ||
| 2071 | + */ | ||
| 2072 | + Relation relation = heap_open(ProcedureRelationId, AccessShareLock); | ||
| 2073 | + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); | ||
| 2074 | + if (!HeapTupleIsValid(tup)) { | ||
| 2075 | + elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); | ||
| 2076 | + } | ||
| 2077 | + | ||
| 2078 | + if (!heap_attisnull(tup, Anum_pg_proc_proconfig, NULL) || u_sess->SPI_cxt.is_proconfig_set) { | ||
| 2079 | + u_sess->SPI_cxt.is_proconfig_set = true; | ||
| 2080 | + node->atomic = true; | ||
| 2081 | + } | ||
| 2082 | + | ||
| 2083 | + proIsProcedure = PROC_IS_PRO(((Form_pg_proc)GETSTRUCT(tup))->prokind); | ||
| 2084 | + | ||
| 2085 | + heap_close(relation, AccessShareLock); | ||
| 2086 | + | ||
| 2087 | + /* If proisprocedure is true means it was a stored procedure. */ | ||
| 2088 | + u_sess->SPI_cxt.is_stp = savedIsStp && proIsProcedure; | ||
| 2089 | + ReleaseSysCache(tup); | ||
| 2090 | + } | ||
| 2045 | 2091 | ||
| 2046 | /* Guard against stack overflow due to overly complex expressions */ | 2092 | /* Guard against stack overflow due to overly complex expressions */ |
| 2047 | check_stack_depth(); | 2093 | check_stack_depth(); |
| @@ -2058,6 +2104,10 @@ static Datum ExecMakeFunctionResultNoSets( | |||
| 2058 | /* init the number of arguments to a function*/ | 2104 | /* init the number of arguments to a function*/ |
| 2059 | InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); | 2105 | InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); |
| 2060 | 2106 | ||
| 2107 | + if (supportTransaction) { | ||
| 2108 | + fcinfo->context = (Node*)node; | ||
| 2109 | + } | ||
| 2110 | + | ||
| 2061 | if (has_cursor_return) { | 2111 | if (has_cursor_return) { |
| 2062 | /* init returnCursor to store out-args cursor info on ExprContext*/ | 2112 | /* init returnCursor to store out-args cursor info on ExprContext*/ |
| 2063 | fcinfo->refcursor_data.returnCursor = | 2113 | fcinfo->refcursor_data.returnCursor = |
| @@ -2152,6 +2202,9 @@ static Datum ExecMakeFunctionResultNoSets( | |||
| 2152 | pfree_ext(var_dno); | 2202 | pfree_ext(var_dno); |
| 2153 | } | 2203 | } |
| 2154 | 2204 | ||
| 2205 | + u_sess->SPI_cxt.is_stp = savedIsStp; | ||
| 2206 | + u_sess->SPI_cxt.is_proconfig_set = savedProConfigIsSet; | ||
| 2207 | + | ||
| 2155 | return result; | 2208 | return result; |
| 2156 | } | 2209 | } |
| 2157 | 2210 | ||
| @@ -2234,9 +2287,53 @@ Tuplestorestate* ExecMakeTableFunctionResult( | |||
| 2234 | bool first_time = true; | 2287 | bool first_time = true; |
| 2235 | int* var_dno = NULL; | 2288 | int* var_dno = NULL; |
| 2236 | bool has_refcursor = false; | 2289 | bool has_refcursor = false; |
| 2290 | + HeapTuple tup; | ||
| 2291 | + FuncExpr* fexpr = NULL; | ||
| 2292 | + bool savedIsStp = u_sess->SPI_cxt.is_stp; | ||
| 2293 | + bool savedProConfigIsSet = u_sess->SPI_cxt.is_proconfig_set; | ||
| 2294 | + bool proIsProcedure = false; | ||
| 2295 | + bool supportTransaction = false; | ||
| 2237 | 2296 | ||
| 2238 | callerContext = CurrentMemoryContext; | 2297 | callerContext = CurrentMemoryContext; |
| 2239 | 2298 | ||
| 2299 | + | ||
| 2300 | + if (IS_PGXC_COORDINATOR) { | ||
| 2301 | + supportTransaction = true; | ||
| 2302 | + } | ||
| 2303 | + | ||
| 2304 | + supportTransaction = true; | ||
| 2305 | + | ||
| 2306 | + | ||
| 2307 | + if (supportTransaction && IsA(funcexpr->expr, FuncExpr)) { | ||
| 2308 | + fexpr = (FuncExpr*)(funcexpr->expr); | ||
| 2309 | + node->atomic = (!u_sess->SPI_cxt.is_toplevel_stp || IsTransactionBlock()); | ||
| 2310 | + | ||
| 2311 | + /* | ||
| 2312 | + * If proconfig is set we can't allow transaction commands because of the | ||
| 2313 | + * way the GUC stacking works. The transaction boundary would have to pop | ||
| 2314 | + * the proconfig setting off the stack. That restriction could be lefted | ||
| 2315 | + * by redesigning the GUC nesting mechanism a bit. | ||
| 2316 | + */ | ||
| 2317 | + Relation relation = heap_open(ProcedureRelationId, AccessShareLock); | ||
| 2318 | + tup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); | ||
| 2319 | + if (!HeapTupleIsValid(tup)) { | ||
| 2320 | + elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); | ||
| 2321 | + } | ||
| 2322 | + | ||
| 2323 | + if (!heap_attisnull(tup, Anum_pg_proc_proconfig, NULL) || u_sess->SPI_cxt.is_proconfig_set) { | ||
| 2324 | + u_sess->SPI_cxt.is_proconfig_set = true; | ||
| 2325 | + node->atomic = true; | ||
| 2326 | + } | ||
| 2327 | + | ||
| 2328 | + proIsProcedure = PROC_IS_PRO(((Form_pg_proc)GETSTRUCT(tup))->prokind); | ||
| 2329 | + | ||
| 2330 | + heap_close(relation, AccessShareLock); | ||
| 2331 | + | ||
| 2332 | + /* If proisprocedure is true means it was a stored procedure. */ | ||
| 2333 | + u_sess->SPI_cxt.is_stp = savedIsStp && proIsProcedure; | ||
| 2334 | + ReleaseSysCache(tup); | ||
| 2335 | + } | ||
| 2336 | + | ||
| 2240 | if (unlikely(funcexpr == NULL)) { | 2337 | if (unlikely(funcexpr == NULL)) { |
| 2241 | ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The input function expression is NULL."))); | 2338 | ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The input function expression is NULL."))); |
| 2242 | } | 2339 | } |
| @@ -2351,7 +2448,11 @@ Tuplestorestate* ExecMakeTableFunctionResult( | |||
| 2351 | } else { | 2448 | } else { |
| 2352 | /* Treat funcexpr as a generic expression */ | 2449 | /* Treat funcexpr as a generic expression */ |
| 2353 | direct_function_call = false; | 2450 | direct_function_call = false; |
| 2354 | - InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); | 2451 | + if (supportTransaction) { |
| 2452 | + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, (Node*)node, NULL); | ||
| 2453 | + } else { | ||
| 2454 | + InitFunctionCallInfoData(fcinfo, NULL, 0, InvalidOid, NULL, NULL); | ||
| 2455 | + } | ||
| 2355 | } | 2456 | } |
| 2356 | 2457 | ||
| 2357 | /* | 2458 | /* |
| @@ -2592,6 +2693,9 @@ no_function_result: | |||
| 2592 | pfree_ext(var_dno); | 2693 | pfree_ext(var_dno); |
| 2593 | } | 2694 | } |
| 2594 | 2695 | ||
| 2696 | + u_sess->SPI_cxt.is_stp = savedIsStp; | ||
| 2697 | + u_sess->SPI_cxt.is_proconfig_set = savedProConfigIsSet; | ||
| 2698 | + | ||
| 2595 | /* All done, pass back the tuplestore */ | 2699 | /* All done, pass back the tuplestore */ |
| 2596 | return rsinfo.setResult; | 2700 | return rsinfo.setResult; |
| 2597 | } | 2701 | } |
| @@ -77,6 +77,11 @@ static void CopySPI_Plan(SPIPlanPtr newplan, SPIPlanPtr plan, MemoryContext plan | |||
| 77 | 77 | ||
| 78 | /* =================== interface functions =================== */ | 78 | /* =================== interface functions =================== */ |
| 79 | int SPI_connect(CommandDest dest, void (*spiCallbackfn)(void *), void *clientData) | 79 | int SPI_connect(CommandDest dest, void (*spiCallbackfn)(void *), void *clientData) |
| 80 | +{ | ||
| 81 | + return SPI_connect_ext(dest, spiCallbackfn, clientData, 0); | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +int SPI_connect_ext(CommandDest dest, void (*spiCallbackfn)(void *), void *clientData, int options) | ||
| 80 | { | 85 | { |
| 81 | int new_depth; | 86 | int new_depth; |
| 82 | /* | 87 | /* |
| @@ -93,8 +98,12 @@ int SPI_connect(CommandDest dest, void (*spiCallbackfn)(void *), void *clientDat | |||
| 93 | u_sess->SPI_cxt._connected != -1 ? "init level is not -1." : "stack depth is not zero."))); | 98 | u_sess->SPI_cxt._connected != -1 ? "init level is not -1." : "stack depth is not zero."))); |
| 94 | } | 99 | } |
| 95 | new_depth = 16; | 100 | new_depth = 16; |
| 101 | + /* | ||
| 102 | + * Need TopMemoryContext because commit is allowed in stored procedure and it will clear all memory | ||
| 103 | + * context from TopTransaction. Therefor,need to use TopMemoryContext. | ||
| 104 | + */ | ||
| 96 | u_sess->SPI_cxt._stack = | 105 | u_sess->SPI_cxt._stack = |
| 97 | - (_SPI_connection *)MemoryContextAlloc(u_sess->top_transaction_mem_cxt, new_depth * sizeof(_SPI_connection)); | 106 | + (_SPI_connection *)MemoryContextAlloc(u_sess->top_mem_cxt, new_depth * sizeof(_SPI_connection)); |
| 98 | u_sess->SPI_cxt._stack_depth = new_depth; | 107 | u_sess->SPI_cxt._stack_depth = new_depth; |
| 99 | } else { | 108 | } else { |
| 100 | if (u_sess->SPI_cxt._stack_depth <= 0 || u_sess->SPI_cxt._stack_depth <= u_sess->SPI_cxt._connected) { | 109 | if (u_sess->SPI_cxt._stack_depth <= 0 || u_sess->SPI_cxt._stack_depth <= u_sess->SPI_cxt._connected) { |
| @@ -125,18 +134,28 @@ int SPI_connect(CommandDest dest, void (*spiCallbackfn)(void *), void *clientDat | |||
| 125 | u_sess->SPI_cxt._current->dest = dest; | 134 | u_sess->SPI_cxt._current->dest = dest; |
| 126 | u_sess->SPI_cxt._current->spiCallback = (void (*)(void *))spiCallbackfn; | 135 | u_sess->SPI_cxt._current->spiCallback = (void (*)(void *))spiCallbackfn; |
| 127 | u_sess->SPI_cxt._current->clientData = clientData; | 136 | u_sess->SPI_cxt._current->clientData = clientData; |
| 137 | + u_sess->SPI_cxt._current->atomic = (options & SPI_OPT_NOATOMIC) ? false : true; | ||
| 138 | + u_sess->SPI_cxt._current->internal_xact = false; | ||
| 128 | 139 | ||
| 129 | /* | 140 | /* |
| 130 | * Create memory contexts for this procedure | 141 | * Create memory contexts for this procedure |
| 142 | + * | ||
| 143 | + * In atomic contexts (the normal case), we use TopTransactionContext, | ||
| 144 | + * otherwise PortalContext, so that it lives across transaction | ||
| 145 | + * boundaries. | ||
| 131 | * | 146 | * |
| 132 | - * XXX it would be better to use t_thrd.mem_cxt.portal_mem_cxt as the parent context, but | 147 | + * XXX it would be better to use PortalContext as the parent context in |
| 133 | - * we may not be inside a portal (consider deferred-trigger execution). | 148 | + * all cases, but we may not be inside a portal (consider deferred-trigger |
| 134 | - * Perhaps t_thrd.mem_cxt.cur_transaction_mem_cxt would do? For now it doesn't matter | 149 | + * execution). Perhaps CurTransactionContext could be an option? For now |
| 135 | - * because we clean up explicitly in AtEOSubXact_SPI(). | 150 | + * it doesn't matter because we clean up explicitly in ATEOSubXact_SPI(). |
| 136 | */ | 151 | */ |
| 137 | - u_sess->SPI_cxt._current->procCxt = AllocSetContextCreate(u_sess->top_transaction_mem_cxt, "SPI Proc", | 152 | + u_sess->SPI_cxt._current->procCxt = |
| 153 | + AllocSetContextCreate(u_sess->SPI_cxt._current->atomic ? u_sess->top_transaction_mem_cxt : | ||
| 154 | + t_thrd.mem_cxt.portal_mem_cxt, "SPI Proc", | ||
| 138 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); | 155 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); |
| 139 | - u_sess->SPI_cxt._current->execCxt = AllocSetContextCreate(u_sess->top_transaction_mem_cxt, "SPI Exec", | 156 | + u_sess->SPI_cxt._current->execCxt = |
| 157 | + AllocSetContextCreate(u_sess->SPI_cxt._current->atomic ? u_sess->top_transaction_mem_cxt : | ||
| 158 | + u_sess->SPI_cxt._current->procCxt, "SPI Exec", | ||
| 140 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); | 159 | ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); |
| 141 | /* ... and switch to procedure's context */ | 160 | /* ... and switch to procedure's context */ |
| 142 | u_sess->SPI_cxt._current->savedcxt = MemoryContextSwitchTo(u_sess->SPI_cxt._current->procCxt); | 161 | u_sess->SPI_cxt._current->savedcxt = MemoryContextSwitchTo(u_sess->SPI_cxt._current->procCxt); |
| @@ -185,11 +204,138 @@ int SPI_finish(void) | |||
| 185 | return SPI_OK_FINISH; | 204 | return SPI_OK_FINISH; |
| 186 | } | 205 | } |
| 187 | 206 | ||
| 207 | +void SPI_start_transaction(void) | ||
| 208 | +{ | ||
| 209 | + MemoryContext oldContext = CurrentMemoryContext; | ||
| 210 | + | ||
| 211 | + StartTransactionCommand(true); | ||
| 212 | + MemoryContextSwitchTo(oldContext); | ||
| 213 | +} | ||
| 214 | + | ||
| 215 | +void SPI_commit(void) | ||
| 216 | +{ | ||
| 217 | + MemoryContext oldContext = CurrentMemoryContext; | ||
| 218 | + | ||
| 219 | + | ||
| 220 | + /* Can not commit at non-CN nodes */ | ||
| 221 | + if (!IS_PGXC_COORDINATOR || IsConnFromCoord()) { | ||
| 222 | + ereport(ERROR, | ||
| 223 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 224 | + errmsg("cannot commit at non-CN node"))); | ||
| 225 | + } | ||
| 226 | + | ||
| 227 | + | ||
| 228 | + /* If commit is not within stored procedure report error */ | ||
| 229 | + if (!u_sess->SPI_cxt.is_stp) { | ||
| 230 | + ereport(ERROR, | ||
| 231 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 232 | + errmsg("cannot commit within function"))); | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + /* Cannot commit if it's atomic is true */ | ||
| 236 | + if (u_sess->SPI_cxt._current->atomic) { | ||
| 237 | + ereport(ERROR, | ||
| 238 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 239 | + errmsg("invalid transaction termination"))); | ||
| 240 | + } | ||
| 241 | + | ||
| 242 | + /* | ||
| 243 | + * Hold any pinned portals that any PLs might be using. We have to do | ||
| 244 | + * this before changing trasnaction state, since this will run | ||
| 245 | + * user-defined code that might throw an error. | ||
| 246 | + */ | ||
| 247 | + HoldPinnedPortals(); | ||
| 248 | + | ||
| 249 | + /* | ||
| 250 | + * This restriction is required by PLs implemented on top of SPI. They | ||
| 251 | + * use subtransactions to establish exception blocks that are supposed to | ||
| 252 | + * be rolled back together if there is an error. Terminating the | ||
| 253 | + * top-level transaction in such a block violates that idea. A future PL | ||
| 254 | + * implementation might have different ideas about this, in which case | ||
| 255 | + * this restriction would have to be refined or the check possibly be | ||
| 256 | + * moved out of SPI into the PLs. | ||
| 257 | + */ | ||
| 258 | + u_sess->SPI_cxt._current->internal_xact = true; | ||
| 259 | + | ||
| 260 | + while (ActiveSnapshotSet()) { | ||
| 261 | + PopActiveSnapshot(); | ||
| 262 | + } | ||
| 263 | + | ||
| 264 | + CommitTransactionCommand(true); | ||
| 265 | + MemoryContextSwitchTo(oldContext); | ||
| 266 | + | ||
| 267 | + u_sess->SPI_cxt._current->internal_xact = false; | ||
| 268 | +} | ||
| 269 | + | ||
| 270 | +void SPI_rollback(void) | ||
| 271 | +{ | ||
| 272 | + MemoryContext oldContext = CurrentMemoryContext; | ||
| 273 | + | ||
| 274 | + | ||
| 275 | + /* Can not commit at non-CN nodes */ | ||
| 276 | + if (!IS_PGXC_COORDINATOR || IsConnFromCoord()) { | ||
| 277 | + ereport(ERROR, | ||
| 278 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 279 | + errmsg("cannot rollback at non-CN node"))); | ||
| 280 | + } | ||
| 281 | + | ||
| 282 | + | ||
| 283 | + /* If commit is not within stored procedure report error */ | ||
| 284 | + if (!u_sess->SPI_cxt.is_stp) { | ||
| 285 | + ereport(ERROR, | ||
| 286 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 287 | + errmsg("cannot rollback within function"))); | ||
| 288 | + } | ||
| 289 | + | ||
| 290 | + /* Cannot commit if it's atomic is true */ | ||
| 291 | + if (u_sess->SPI_cxt._current->atomic) { | ||
| 292 | + ereport(ERROR, | ||
| 293 | + (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION), | ||
| 294 | + errmsg("invalid transaction termination"))); | ||
| 295 | + } | ||
| 296 | + | ||
| 297 | + /* | ||
| 298 | + * Hold any pinned portals that any PLs might be using. We have to do | ||
| 299 | + * this before changing trasnaction state, since this will run | ||
| 300 | + * user-defined code that might throw an error. | ||
| 301 | + */ | ||
| 302 | + HoldPinnedPortals(); | ||
| 303 | + | ||
| 304 | + /* see under SPI_commit() */ | ||
| 305 | + u_sess->SPI_cxt._current->internal_xact = true; | ||
| 306 | + | ||
| 307 | + AbortCurrentTransaction(true); | ||
| 308 | + MemoryContextSwitchTo(oldContext); | ||
| 309 | + | ||
| 310 | + u_sess->SPI_cxt._current->internal_xact = false; | ||
| 311 | +} | ||
| 312 | + | ||
| 313 | +/* | ||
| 314 | + * Clean up SPI state. Called on trasnaction end (of non-SPI-internal | ||
| 315 | + * trasnactions) and when retruning to the main loop on error. | ||
| 316 | + */ | ||
| 317 | +void SPICleanup(void) | ||
| 318 | +{ | ||
| 319 | + u_sess->SPI_cxt._current = u_sess->SPI_cxt._stack = NULL; | ||
| 320 | + u_sess->SPI_cxt._stack_depth = 0; | ||
| 321 | + u_sess->SPI_cxt._connected = u_sess->SPI_cxt._curid = -1; | ||
| 322 | + SPI_processed = 0; | ||
| 323 | + u_sess->SPI_cxt.lastoid = InvalidOid; | ||
| 324 | + SPI_tuptable = NULL; | ||
| 325 | +} | ||
| 326 | + | ||
| 188 | /* | 327 | /* |
| 189 | * Clean up SPI state at transaction commit or abort. | 328 | * Clean up SPI state at transaction commit or abort. |
| 190 | */ | 329 | */ |
| 191 | -void AtEOXact_SPI(bool isCommit) | 330 | +void AtEOXact_SPI(bool isCommit, bool stpRollback, bool stpCommit) |
| 192 | { | 331 | { |
| 332 | + /* | ||
| 333 | + * Do nothing if the trasnaction end was initiated by SPI. | ||
| 334 | + */ | ||
| 335 | + if (stpRollback || stpCommit) { | ||
| 336 | + return; | ||
| 337 | + } | ||
| 338 | + | ||
| 193 | /* | 339 | /* |
| 194 | * Note that memory contexts belonging to SPI stack entries will be freed | 340 | * Note that memory contexts belonging to SPI stack entries will be freed |
| 195 | * automatically, so we can ignore them here. We just need to restore our | 341 | * automatically, so we can ignore them here. We just need to restore our |
| @@ -200,12 +346,7 @@ void AtEOXact_SPI(bool isCommit) | |||
| 200 | errhint("Check for missing \"SPI_finish\" calls."))); | 346 | errhint("Check for missing \"SPI_finish\" calls."))); |
| 201 | } | 347 | } |
| 202 | 348 | ||
| 203 | - u_sess->SPI_cxt._current = u_sess->SPI_cxt._stack = NULL; | 349 | + SPICleanup(); |
| 204 | - u_sess->SPI_cxt._stack_depth = 0; | ||
| 205 | - u_sess->SPI_cxt._connected = u_sess->SPI_cxt._curid = -1; | ||
| 206 | - SPI_processed = 0; | ||
| 207 | - u_sess->SPI_cxt.lastoid = InvalidOid; | ||
| 208 | - SPI_tuptable = NULL; | ||
| 209 | } | 350 | } |
| 210 | 351 | ||
| 211 | /* | 352 | /* |
| @@ -214,8 +355,12 @@ void AtEOXact_SPI(bool isCommit) | |||
| 214 | * During commit, there shouldn't be any unclosed entries remaining from | 355 | * During commit, there shouldn't be any unclosed entries remaining from |
| 215 | * the current subtransaction; we emit a warning if any are found. | 356 | * the current subtransaction; we emit a warning if any are found. |
| 216 | */ | 357 | */ |
| 217 | -void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid) | 358 | +void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid, bool stpRollback, bool stpCommit) |
| 218 | { | 359 | { |
| 360 | + if (stpRollback || stpCommit) { | ||
| 361 | + return; | ||
| 362 | + } | ||
| 363 | + | ||
| 219 | bool found = false; | 364 | bool found = false; |
| 220 | 365 | ||
| 221 | while (u_sess->SPI_cxt._connected >= 0) { | 366 | while (u_sess->SPI_cxt._connected >= 0) { |
| @@ -225,6 +370,10 @@ void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid) | |||
| 225 | break; /* couldn't be any underneath it either */ | 370 | break; /* couldn't be any underneath it either */ |
| 226 | } | 371 | } |
| 227 | 372 | ||
| 373 | + if (connection->internal_xact) { | ||
| 374 | + break; | ||
| 375 | + } | ||
| 376 | + | ||
| 228 | found = true; | 377 | found = true; |
| 229 | /* | 378 | /* |
| 230 | * Release procedure memory explicitly (see note in SPI_connect) | 379 | * Release procedure memory explicitly (see note in SPI_connect) |
| @@ -1873,6 +2022,11 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot sn | |||
| 1873 | CachedPlan *cplan = NULL; | 2022 | CachedPlan *cplan = NULL; |
| 1874 | ListCell *lc1 = NULL; | 2023 | ListCell *lc1 = NULL; |
| 1875 | bool tmp_enable_light_proxy = u_sess->attr.attr_sql.enable_light_proxy; | 2024 | bool tmp_enable_light_proxy = u_sess->attr.attr_sql.enable_light_proxy; |
| 2025 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 2026 | + | ||
| 2027 | + if (!RecoveryInProgress()) { | ||
| 2028 | + oldTransactionId = GetTopTransactionId(); | ||
| 2029 | + } | ||
| 1876 | 2030 | ||
| 1877 | /* not allow Light CN */ | 2031 | /* not allow Light CN */ |
| 1878 | u_sess->attr.attr_sql.enable_light_proxy = false; | 2032 | u_sess->attr.attr_sql.enable_light_proxy = false; |
| @@ -2118,7 +2272,9 @@ static int _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, Snapshot sn | |||
| 2118 | } | 2272 | } |
| 2119 | 2273 | ||
| 2120 | /* Done with this plan, so release refcount */ | 2274 | /* Done with this plan, so release refcount */ |
| 2121 | - ReleaseCachedPlan(cplan, plan->saved); | 2275 | + if ((!RecoveryInProgress()) && (oldTransactionId == GetTopTransactionId())) { |
| 2276 | + ReleaseCachedPlan(cplan, plan->saved); | ||
| 2277 | + } | ||
| 2122 | cplan = NULL; | 2278 | cplan = NULL; |
| 2123 | 2279 | ||
| 2124 | /* | 2280 | /* |
| @@ -2228,6 +2384,7 @@ static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount, bo | |||
| 2228 | int operation = queryDesc->operation; | 2384 | int operation = queryDesc->operation; |
| 2229 | int eflags; | 2385 | int eflags; |
| 2230 | int res; | 2386 | int res; |
| 2387 | + TransactionId oldTransactionId = InvalidTransactionId; | ||
| 2231 | 2388 | ||
| 2232 | switch (operation) { | 2389 | switch (operation) { |
| 2233 | case CMD_SELECT: | 2390 | case CMD_SELECT: |
| @@ -2275,6 +2432,9 @@ static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount, bo | |||
| 2275 | eflags = EXEC_FLAG_SKIP_TRIGGERS; | 2432 | eflags = EXEC_FLAG_SKIP_TRIGGERS; |
| 2276 | } | 2433 | } |
| 2277 | 2434 | ||
| 2435 | + if (!RecoveryInProgress()) { | ||
| 2436 | + oldTransactionId = GetTopTransactionId(); | ||
| 2437 | + } | ||
| 2278 | ExecutorStart(queryDesc, eflags); | 2438 | ExecutorStart(queryDesc, eflags); |
| 2279 | 2439 | ||
| 2280 | bool forced_control = !from_lock && IS_PGXC_COORDINATOR && | 2440 | bool forced_control = !from_lock && IS_PGXC_COORDINATOR && |
| @@ -2351,6 +2511,17 @@ static int _SPI_pquery(QueryDesc *queryDesc, bool fire_triggers, long tcount, bo | |||
| 2351 | } | 2511 | } |
| 2352 | } | 2512 | } |
| 2353 | 2513 | ||
| 2514 | + /* | ||
| 2515 | + * If there are commit/rollback within stored proedure. Snapshot has already free during commit/rollback process. | ||
| 2516 | + * Therefor, need to set queryDesc snapshots to NULL. Otherwise the reference will be stale pointers. | ||
| 2517 | + */ | ||
| 2518 | + if ((!RecoveryInProgress()) && (oldTransactionId != GetTopTransactionId())) { | ||
| 2519 | + queryDesc->snapshot = NULL; | ||
| 2520 | + queryDesc->crosscheck_snapshot = NULL; | ||
| 2521 | + queryDesc->estate->es_snapshot = NULL; | ||
| 2522 | + queryDesc->estate->es_crosscheck_snapshot = NULL; | ||
| 2523 | + } | ||
| 2524 | + | ||
| 2354 | ExecutorFinish(queryDesc); | 2525 | ExecutorFinish(queryDesc); |
| 2355 | ExecutorEnd(queryDesc); | 2526 | ExecutorEnd(queryDesc); |
| 2356 | /* FreeQueryDesc is done by the caller */ | 2527 | /* FreeQueryDesc is done by the caller */ |
| @@ -277,7 +277,7 @@ typedef struct GTMCallbackItem { | |||
| 277 | 277 | ||
| 278 | /* local function prototypes */ | 278 | /* local function prototypes */ |
| 279 | static void AssignTransactionId(TransactionState s); | 279 | static void AssignTransactionId(TransactionState s); |
| 280 | -static void AbortTransaction(bool PerfectRollback); | 280 | +static void AbortTransaction(bool PerfectRollback = false, bool stpRollback = false); |
| 281 | static void AtAbort_Memory(void); | 281 | static void AtAbort_Memory(void); |
| 282 | static void AtCleanup_Memory(void); | 282 | static void AtCleanup_Memory(void); |
| 283 | static void AtAbort_ResourceOwner(void); | 283 | static void AtAbort_ResourceOwner(void); |
| @@ -294,13 +294,13 @@ static void CleanSequenceCallbacks(void); | |||
| 294 | static void CallSequenceCallbacks(GTMEvent event); | 294 | static void CallSequenceCallbacks(GTMEvent event); |
| 295 | 295 | ||
| 296 | static void CleanupTransaction(void); | 296 | static void CleanupTransaction(void); |
| 297 | -static void CommitTransaction(void); | 297 | +static void CommitTransaction(bool stpCommit = false); |
| 298 | static TransactionId RecordTransactionAbort(bool isSubXact); | 298 | static TransactionId RecordTransactionAbort(bool isSubXact); |
| 299 | static void StartTransaction(bool begin_on_gtm); | 299 | static void StartTransaction(bool begin_on_gtm); |
| 300 | 300 | ||
| 301 | static void StartSubTransaction(void); | 301 | static void StartSubTransaction(void); |
| 302 | -static void CommitSubTransaction(void); | 302 | +static void CommitSubTransaction(bool stpCommit = false); |
| 303 | -static void AbortSubTransaction(void); | 303 | +static void AbortSubTransaction(bool stpRollback = false); |
| 304 | static void CleanupSubTransaction(void); | 304 | static void CleanupSubTransaction(void); |
| 305 | static void PushTransaction(void); | 305 | static void PushTransaction(void); |
| 306 | static void PopTransaction(void); | 306 | static void PopTransaction(void); |
| @@ -316,7 +316,7 @@ static void ShowTransactionState(const char* str); | |||
| 316 | static void ShowTransactionStateRec(TransactionState state); | 316 | static void ShowTransactionStateRec(TransactionState state); |
| 317 | static const char* BlockStateAsString(TBlockState blockState); | 317 | static const char* BlockStateAsString(TBlockState blockState); |
| 318 | static const char* TransStateAsString(TransState state); | 318 | static const char* TransStateAsString(TransState state); |
| 319 | -static void PrepareTransaction(void); | 319 | +static void PrepareTransaction(bool stpCommit = false); |
| 320 | 320 | ||
| 321 | extern void print_leak_warning_at_commit(); | 321 | extern void print_leak_warning_at_commit(); |
| 322 | 322 | ||
| @@ -2365,7 +2365,7 @@ void ThreadLocalFlagCleanUp() | |||
| 2365 | * | 2365 | * |
| 2366 | * NB: if you change this routine, better look at PrepareTransaction too! | 2366 | * NB: if you change this routine, better look at PrepareTransaction too! |
| 2367 | */ | 2367 | */ |
| 2368 | -static void CommitTransaction(void) | 2368 | +static void CommitTransaction(bool stpCommit) |
| 2369 | { | 2369 | { |
| 2370 | u_sess->need_report_top_xid = false; | 2370 | u_sess->need_report_top_xid = false; |
| 2371 | TransactionState s = CurrentTransactionState; | 2371 | TransactionState s = CurrentTransactionState; |
| @@ -2410,6 +2410,14 @@ static void CommitTransaction(void) | |||
| 2410 | t_thrd.xact_cxt.handlesDestroyedInCancelQuery = false; | 2410 | t_thrd.xact_cxt.handlesDestroyedInCancelQuery = false; |
| 2411 | ThreadLocalFlagCleanUp(); | 2411 | ThreadLocalFlagCleanUp(); |
| 2412 | 2412 | ||
| 2413 | + /* | ||
| 2414 | + * When commit within nested store procedure, it will create a plan cache. | ||
| 2415 | + * During commit time, need to clean up those plan cache. | ||
| 2416 | + */ | ||
| 2417 | + if (stpCommit) { | ||
| 2418 | + ResourceOwnerDecrementNPlanRefs(t_thrd.utils_cxt.CurrentResourceOwner, true); | ||
| 2419 | + } | ||
| 2420 | + | ||
| 2413 | 2421 | ||
| 2414 | /* | 2422 | /* |
| 2415 | * If we are a Coordinator and currently serving the client, | 2423 | * If we are a Coordinator and currently serving the client, |
| @@ -2486,7 +2494,7 @@ static void CommitTransaction(void) | |||
| 2486 | */ | 2494 | */ |
| 2487 | Assert(GlobalTransactionIdIsValid(s->transactionId)); | 2495 | Assert(GlobalTransactionIdIsValid(s->transactionId)); |
| 2488 | 2496 | ||
| 2489 | - PrepareTransaction(); | 2497 | + PrepareTransaction(stpCommit); |
| 2490 | s->blockState = TBLOCK_DEFAULT; | 2498 | s->blockState = TBLOCK_DEFAULT; |
| 2491 | 2499 | ||
| 2492 | /* | 2500 | /* |
| @@ -2519,7 +2527,7 @@ static void CommitTransaction(void) | |||
| 2519 | * If there weren't any, we are done ... otherwise loop back to check | 2527 | * If there weren't any, we are done ... otherwise loop back to check |
| 2520 | * if they queued deferred triggers. Lather, rinse, repeat. | 2528 | * if they queued deferred triggers. Lather, rinse, repeat. |
| 2521 | */ | 2529 | */ |
| 2522 | - if (!PreCommit_Portals(false)) | 2530 | + if (!PreCommit_Portals(false, stpCommit)) |
| 2523 | break; | 2531 | break; |
| 2524 | } | 2532 | } |
| 2525 | 2533 | ||
| @@ -2848,9 +2856,11 @@ static void CommitTransaction(void) | |||
| 2848 | 2856 | ||
| 2849 | AtCommit_Notify(); | 2857 | AtCommit_Notify(); |
| 2850 | AtEOXact_GUC(true, 1); | 2858 | AtEOXact_GUC(true, 1); |
| 2851 | - AtEOXact_SPI(true); | 2859 | + AtEOXact_SPI(true, false, stpCommit); |
| 2852 | AtEOXact_on_commit_actions(true); | 2860 | AtEOXact_on_commit_actions(true); |
| 2853 | - AtEOXact_Namespace(true); | 2861 | + if (!stpCommit){ |
| 2862 | + AtEOXact_Namespace(true); | ||
| 2863 | + } | ||
| 2854 | AtEOXact_SMgr(); | 2864 | AtEOXact_SMgr(); |
| 2855 | AtEOXact_Files(); | 2865 | AtEOXact_Files(); |
| 2856 | AtEOXact_ComboCid(); | 2866 | AtEOXact_ComboCid(); |
| @@ -3093,7 +3103,7 @@ bool AtEOXact_GlobalTxn(bool commit, bool is_write) | |||
| 3093 | * If PrepareTransaction is called during an implicit 2PC, do not release ressources, | 3103 | * If PrepareTransaction is called during an implicit 2PC, do not release ressources, |
| 3094 | * this is made by CommitTransaction when transaction has been committed on Nodes. | 3104 | * this is made by CommitTransaction when transaction has been committed on Nodes. |
| 3095 | */ | 3105 | */ |
| 3096 | -static void PrepareTransaction(void) | 3106 | +static void PrepareTransaction(bool stpCommit) |
| 3097 | { | 3107 | { |
| 3098 | u_sess->need_report_top_xid = false; | 3108 | u_sess->need_report_top_xid = false; |
| 3099 | TransactionState s = CurrentTransactionState; | 3109 | TransactionState s = CurrentTransactionState; |
| @@ -3198,7 +3208,7 @@ static void PrepareTransaction(void) | |||
| 3198 | * If there weren't any, we are done ... otherwise loop back to check | 3208 | * If there weren't any, we are done ... otherwise loop back to check |
| 3199 | * if they queued deferred triggers. Lather, rinse, repeat. | 3209 | * if they queued deferred triggers. Lather, rinse, repeat. |
| 3200 | */ | 3210 | */ |
| 3201 | - if (!PreCommit_Portals(true)) | 3211 | + if (!PreCommit_Portals(true, stpCommit)) |
| 3202 | break; | 3212 | break; |
| 3203 | } | 3213 | } |
| 3204 | 3214 | ||
| @@ -3392,9 +3402,16 @@ static void PrepareTransaction(void) | |||
| 3392 | 3402 | ||
| 3393 | /* PREPARE acts the same as COMMIT as far as GUC is concerned */ | 3403 | /* PREPARE acts the same as COMMIT as far as GUC is concerned */ |
| 3394 | AtEOXact_GUC(true, 1); | 3404 | AtEOXact_GUC(true, 1); |
| 3395 | - AtEOXact_SPI(true); | 3405 | + AtEOXact_SPI(true, false, stpCommit); |
| 3396 | AtEOXact_on_commit_actions(true); | 3406 | AtEOXact_on_commit_actions(true); |
| 3397 | - AtEOXact_Namespace(true); | 3407 | + /* |
| 3408 | + * For commit within stored procedure don't clean up namespace. | ||
| 3409 | + * Otherwise it will throw warning leaked override search path, | ||
| 3410 | + * since we push the search path hasn't pop yet. | ||
| 3411 | + */ | ||
| 3412 | + if (!stpCommit) { | ||
| 3413 | + AtEOXact_Namespace(true); | ||
| 3414 | + } | ||
| 3398 | AtEOXact_SMgr(); | 3415 | AtEOXact_SMgr(); |
| 3399 | AtEOXact_Files(); | 3416 | AtEOXact_Files(); |
| 3400 | AtEOXact_ComboCid(); | 3417 | AtEOXact_ComboCid(); |
| @@ -3468,7 +3485,7 @@ static void PrepareTransaction(void) | |||
| 3468 | 3485 | ||
| 3469 | } | 3486 | } |
| 3470 | 3487 | ||
| 3471 | -static void AbortTransaction(bool PerfectRollback = false) | 3488 | +static void AbortTransaction(bool PerfectRollback, bool stpRollback) |
| 3472 | { | 3489 | { |
| 3473 | u_sess->need_report_top_xid = false; | 3490 | u_sess->need_report_top_xid = false; |
| 3474 | TransactionState s = CurrentTransactionState; | 3491 | TransactionState s = CurrentTransactionState; |
| @@ -3723,7 +3740,7 @@ static void AbortTransaction(bool PerfectRollback = false) | |||
| 3723 | */ | 3740 | */ |
| 3724 | AfterTriggerEndXact(false); /* 'false' means it's abort */ | 3741 | AfterTriggerEndXact(false); /* 'false' means it's abort */ |
| 3725 | CallXactCallbacks(XACT_EVENT_PREROLLBACK_CLEANUP); | 3742 | CallXactCallbacks(XACT_EVENT_PREROLLBACK_CLEANUP); |
| 3726 | - AtAbort_Portals(); | 3743 | + AtAbort_Portals(stpRollback); |
| 3727 | AtEOXact_LargeObject(false); | 3744 | AtEOXact_LargeObject(false); |
| 3728 | AtAbort_Notify(); | 3745 | AtAbort_Notify(); |
| 3729 | AtEOXact_RelationMap(false); | 3746 | AtEOXact_RelationMap(false); |
| @@ -3787,9 +3804,11 @@ static void AbortTransaction(bool PerfectRollback = false) | |||
| 3787 | if (change_user_name) | 3804 | if (change_user_name) |
| 3788 | u_sess->misc_cxt.CurrentUserName = NULL; | 3805 | u_sess->misc_cxt.CurrentUserName = NULL; |
| 3789 | 3806 | ||
| 3790 | - AtEOXact_SPI(false); | 3807 | + AtEOXact_SPI(false, stpRollback, false); |
| 3791 | AtEOXact_on_commit_actions(false); | 3808 | AtEOXact_on_commit_actions(false); |
| 3792 | - AtEOXact_Namespace(false); | 3809 | + if (!stpRollback) { |
| 3810 | + AtEOXact_Namespace(false); | ||
| 3811 | + } | ||
| 3793 | AtEOXact_SMgr(); | 3812 | AtEOXact_SMgr(); |
| 3794 | AtEOXact_Files(); | 3813 | AtEOXact_Files(); |
| 3795 | AtEOXact_ComboCid(); | 3814 | AtEOXact_ComboCid(); |
| @@ -3876,7 +3895,7 @@ static void CleanupTransaction(void) | |||
| 3876 | 3895 | ||
| 3877 | } | 3896 | } |
| 3878 | 3897 | ||
| 3879 | -void StartTransactionCommand(void) | 3898 | +void StartTransactionCommand(bool stpRollback) |
| 3880 | { | 3899 | { |
| 3881 | TransactionState s = CurrentTransactionState; | 3900 | TransactionState s = CurrentTransactionState; |
| 3882 | 3901 | ||
| @@ -3918,6 +3937,9 @@ void StartTransactionCommand(void) | |||
| 3918 | */ | 3937 | */ |
| 3919 | case TBLOCK_ABORT: | 3938 | case TBLOCK_ABORT: |
| 3920 | case TBLOCK_SUBABORT: | 3939 | case TBLOCK_SUBABORT: |
| 3940 | + if (stpRollback) { | ||
| 3941 | + s->blockState = TBLOCK_DEFAULT; | ||
| 3942 | + } | ||
| 3921 | break; | 3943 | break; |
| 3922 | 3944 | ||
| 3923 | /* These cases are invalid. */ | 3945 | /* These cases are invalid. */ |
| @@ -3949,10 +3971,11 @@ void StartTransactionCommand(void) | |||
| 3949 | (void)MemoryContextSwitchTo(t_thrd.mem_cxt.cur_transaction_mem_cxt); | 3971 | (void)MemoryContextSwitchTo(t_thrd.mem_cxt.cur_transaction_mem_cxt); |
| 3950 | } | 3972 | } |
| 3951 | 3973 | ||
| 3952 | -void CommitTransactionCommand(void) | 3974 | +void CommitTransactionCommand(bool stpCommit) |
| 3953 | { | 3975 | { |
| 3954 | TransactionState s = CurrentTransactionState; | 3976 | TransactionState s = CurrentTransactionState; |
| 3955 | TBlockState oldstate = s->blockState; | 3977 | TBlockState oldstate = s->blockState; |
| 3978 | + const int stpownerlevel = 2; | ||
| 3956 | 3979 | ||
| 3957 | switch (s->blockState) { | 3980 | switch (s->blockState) { |
| 3958 | /* | 3981 | /* |
| @@ -3971,7 +3994,7 @@ void CommitTransactionCommand(void) | |||
| 3971 | * transaction commit, and return to the idle state. | 3994 | * transaction commit, and return to the idle state. |
| 3972 | */ | 3995 | */ |
| 3973 | case TBLOCK_STARTED: | 3996 | case TBLOCK_STARTED: |
| 3974 | - CommitTransaction(); | 3997 | + CommitTransaction(stpCommit); |
| 3975 | s->blockState = TBLOCK_DEFAULT; | 3998 | s->blockState = TBLOCK_DEFAULT; |
| 3976 | break; | 3999 | break; |
| 3977 | 4000 | ||
| @@ -3993,6 +4016,29 @@ void CommitTransactionCommand(void) | |||
| 3993 | case TBLOCK_INPROGRESS: | 4016 | case TBLOCK_INPROGRESS: |
| 3994 | case TBLOCK_SUBINPROGRESS: | 4017 | case TBLOCK_SUBINPROGRESS: |
| 3995 | CommandCounterIncrement(); | 4018 | CommandCounterIncrement(); |
| 4019 | + | ||
| 4020 | + if (stpCommit && u_sess->SPI_cxt.portal_stp_exception_counter > 0) { | ||
| 4021 | + int subTransactionCounter = 0; | ||
| 4022 | + Assert(!StreamThreadAmI()); | ||
| 4023 | + | ||
| 4024 | + do { | ||
| 4025 | + MemoryContextSwitchTo(t_thrd.mem_cxt.cur_transaction_mem_cxt); | ||
| 4026 | + CommitSubTransaction(stpCommit); | ||
| 4027 | + s = CurrentTransactionState; | ||
| 4028 | + subTransactionCounter++; | ||
| 4029 | + | ||
| 4030 | + if (IS_PGXC_COORDINATOR && !IsConnFromCoord()) { | ||
| 4031 | + /* CN should send release savepoint command to remote nodes for savepoint name reuse */ | ||
| 4032 | + pgxc_node_remote_savepoint("release s1", EXEC_ON_ALL_NODES, true, false); | ||
| 4033 | + } | ||
| 4034 | + } while (s->blockState == TBLOCK_SUBINPROGRESS); | ||
| 4035 | + | ||
| 4036 | + /* If we had a commit command, finish off the main xact too */ | ||
| 4037 | + Assert(subTransactionCounter == u_sess->SPI_cxt.portal_stp_exception_counter); | ||
| 4038 | + t_thrd.utils_cxt.CurrentResourceOwner = t_thrd.utils_cxt.StpSavedResourceOwner; | ||
| 4039 | + CommitTransaction(stpCommit); | ||
| 4040 | + s->blockState = TBLOCK_DEFAULT; | ||
| 4041 | + } | ||
| 3996 | break; | 4042 | break; |
| 3997 | 4043 | ||
| 3998 | /* | 4044 | /* |
| @@ -4000,7 +4046,7 @@ void CommitTransactionCommand(void) | |||
| 4000 | * idle state. | 4046 | * idle state. |
| 4001 | */ | 4047 | */ |
| 4002 | case TBLOCK_END: | 4048 | case TBLOCK_END: |
| 4003 | - CommitTransaction(); | 4049 | + CommitTransaction(stpCommit); |
| 4004 | s->blockState = TBLOCK_DEFAULT; | 4050 | s->blockState = TBLOCK_DEFAULT; |
| 4005 | break; | 4051 | break; |
| 4006 | 4052 | ||
| @@ -4029,7 +4075,7 @@ void CommitTransactionCommand(void) | |||
| 4029 | * and then clean up. | 4075 | * and then clean up. |
| 4030 | */ | 4076 | */ |
| 4031 | case TBLOCK_ABORT_PENDING: | 4077 | case TBLOCK_ABORT_PENDING: |
| 4032 | - AbortTransaction(true); | 4078 | + AbortTransaction(true, false); |
| 4033 | CleanupTransaction(); | 4079 | CleanupTransaction(); |
| 4034 | s->blockState = TBLOCK_DEFAULT; | 4080 | s->blockState = TBLOCK_DEFAULT; |
| 4035 | break; | 4081 | break; |
| @@ -4050,6 +4096,9 @@ void CommitTransactionCommand(void) | |||
| 4050 | * state.) | 4096 | * state.) |
| 4051 | */ | 4097 | */ |
| 4052 | case TBLOCK_SUBBEGIN: | 4098 | case TBLOCK_SUBBEGIN: |
| 4099 | + if (CurrentTransactionState->nestingLevel == stpownerlevel) { | ||
| 4100 | + t_thrd.utils_cxt.StpSavedResourceOwner = t_thrd.utils_cxt.CurrentResourceOwner; | ||
| 4101 | + } | ||
| 4053 | StartSubTransaction(); | 4102 | StartSubTransaction(); |
| 4054 | s->blockState = TBLOCK_SUBINPROGRESS; | 4103 | s->blockState = TBLOCK_SUBINPROGRESS; |
| 4055 | break; | 4104 | break; |
| @@ -4112,7 +4161,7 @@ void CommitTransactionCommand(void) | |||
| 4112 | 4161 | ||
| 4113 | /* As above, but it's not dead yet, so abort first. */ | 4162 | /* As above, but it's not dead yet, so abort first. */ |
| 4114 | case TBLOCK_SUBABORT_PENDING: | 4163 | case TBLOCK_SUBABORT_PENDING: |
| 4115 | - AbortSubTransaction(); | 4164 | + AbortSubTransaction(stpCommit); |
| 4116 | CleanupSubTransaction(); | 4165 | CleanupSubTransaction(); |
| 4117 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { | 4166 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { |
| 4118 | ereport(WARNING, | 4167 | ereport(WARNING, |
| @@ -4137,7 +4186,7 @@ void CommitTransactionCommand(void) | |||
| 4137 | s->name = NULL; | 4186 | s->name = NULL; |
| 4138 | savepointLevel = s->savepointLevel; | 4187 | savepointLevel = s->savepointLevel; |
| 4139 | 4188 | ||
| 4140 | - AbortSubTransaction(); | 4189 | + AbortSubTransaction(stpCommit); |
| 4141 | CleanupSubTransaction(); | 4190 | CleanupSubTransaction(); |
| 4142 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { | 4191 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { |
| 4143 | ereport(WARNING, | 4192 | ereport(WARNING, |
| @@ -4198,7 +4247,7 @@ void CommitTransactionCommand(void) | |||
| 4198 | } | 4247 | } |
| 4199 | } | 4248 | } |
| 4200 | 4249 | ||
| 4201 | -void AbortCurrentTransaction(void) | 4250 | +void AbortCurrentTransaction(bool stpRollback) |
| 4202 | { | 4251 | { |
| 4203 | TransactionState s = CurrentTransactionState; | 4252 | TransactionState s = CurrentTransactionState; |
| 4204 | 4253 | ||
| @@ -4216,7 +4265,7 @@ void AbortCurrentTransaction(void) | |||
| 4216 | */ | 4265 | */ |
| 4217 | if (s->state == TRANS_START) | 4266 | if (s->state == TRANS_START) |
| 4218 | s->state = TRANS_INPROGRESS; | 4267 | s->state = TRANS_INPROGRESS; |
| 4219 | - AbortTransaction(); | 4268 | + AbortTransaction(false, stpRollback); |
| 4220 | CleanupTransaction(); | 4269 | CleanupTransaction(); |
| 4221 | } | 4270 | } |
| 4222 | break; | 4271 | break; |
| @@ -4226,7 +4275,7 @@ void AbortCurrentTransaction(void) | |||
| 4226 | * & cleanup transaction. | 4275 | * & cleanup transaction. |
| 4227 | */ | 4276 | */ |
| 4228 | case TBLOCK_STARTED: | 4277 | case TBLOCK_STARTED: |
| 4229 | - AbortTransaction(); | 4278 | + AbortTransaction(false, stpRollback); |
| 4230 | CleanupTransaction(); | 4279 | CleanupTransaction(); |
| 4231 | s->blockState = TBLOCK_DEFAULT; | 4280 | s->blockState = TBLOCK_DEFAULT; |
| 4232 | break; | 4281 | break; |
| @@ -4239,7 +4288,7 @@ void AbortCurrentTransaction(void) | |||
| 4239 | * state. | 4288 | * state. |
| 4240 | */ | 4289 | */ |
| 4241 | case TBLOCK_BEGIN: | 4290 | case TBLOCK_BEGIN: |
| 4242 | - AbortTransaction(); | 4291 | + AbortTransaction(false, stpRollback); |
| 4243 | CleanupTransaction(); | 4292 | CleanupTransaction(); |
| 4244 | s->blockState = TBLOCK_DEFAULT; | 4293 | s->blockState = TBLOCK_DEFAULT; |
| 4245 | break; | 4294 | break; |
| @@ -4250,7 +4299,7 @@ void AbortCurrentTransaction(void) | |||
| 4250 | * ABORT state. We will stay in ABORT until we get a ROLLBACK. | 4299 | * ABORT state. We will stay in ABORT until we get a ROLLBACK. |
| 4251 | */ | 4300 | */ |
| 4252 | case TBLOCK_INPROGRESS: | 4301 | case TBLOCK_INPROGRESS: |
| 4253 | - AbortTransaction(); | 4302 | + AbortTransaction(false, stpRollback); |
| 4254 | s->blockState = TBLOCK_ABORT; | 4303 | s->blockState = TBLOCK_ABORT; |
| 4255 | /* CleanupTransaction happens when we exit TBLOCK_ABORT_END */ | 4304 | /* CleanupTransaction happens when we exit TBLOCK_ABORT_END */ |
| 4256 | break; | 4305 | break; |
| @@ -4261,7 +4310,7 @@ void AbortCurrentTransaction(void) | |||
| 4261 | * the transaction). | 4310 | * the transaction). |
| 4262 | */ | 4311 | */ |
| 4263 | case TBLOCK_END: | 4312 | case TBLOCK_END: |
| 4264 | - AbortTransaction(); | 4313 | + AbortTransaction(false, stpRollback); |
| 4265 | CleanupTransaction(); | 4314 | CleanupTransaction(); |
| 4266 | s->blockState = TBLOCK_DEFAULT; | 4315 | s->blockState = TBLOCK_DEFAULT; |
| 4267 | break; | 4316 | break; |
| @@ -4290,7 +4339,7 @@ void AbortCurrentTransaction(void) | |||
| 4290 | * Abort, cleanup, go to idle state. | 4339 | * Abort, cleanup, go to idle state. |
| 4291 | */ | 4340 | */ |
| 4292 | case TBLOCK_ABORT_PENDING: | 4341 | case TBLOCK_ABORT_PENDING: |
| 4293 | - AbortTransaction(); | 4342 | + AbortTransaction(false, stpRollback); |
| 4294 | CleanupTransaction(); | 4343 | CleanupTransaction(); |
| 4295 | s->blockState = TBLOCK_DEFAULT; | 4344 | s->blockState = TBLOCK_DEFAULT; |
| 4296 | break; | 4345 | break; |
| @@ -4301,7 +4350,7 @@ void AbortCurrentTransaction(void) | |||
| 4301 | * the transaction). | 4350 | * the transaction). |
| 4302 | */ | 4351 | */ |
| 4303 | case TBLOCK_PREPARE: | 4352 | case TBLOCK_PREPARE: |
| 4304 | - AbortTransaction(); | 4353 | + AbortTransaction(false, stpRollback); |
| 4305 | CleanupTransaction(); | 4354 | CleanupTransaction(); |
| 4306 | s->blockState = TBLOCK_DEFAULT; | 4355 | s->blockState = TBLOCK_DEFAULT; |
| 4307 | break; | 4356 | break; |
| @@ -4312,8 +4361,29 @@ void AbortCurrentTransaction(void) | |||
| 4312 | * we get ROLLBACK. | 4361 | * we get ROLLBACK. |
| 4313 | */ | 4362 | */ |
| 4314 | case TBLOCK_SUBINPROGRESS: | 4363 | case TBLOCK_SUBINPROGRESS: |
| 4315 | - AbortSubTransaction(); | 4364 | + if (stpRollback && u_sess->SPI_cxt.portal_stp_exception_counter > 0) { |
| 4316 | - s->blockState = TBLOCK_SUBABORT; | 4365 | + int subTransactionCounter = 0; |
| 4366 | + do { | ||
| 4367 | + AbortSubTransaction(stpRollback); | ||
| 4368 | + s->blockState = TBLOCK_SUBABORT; | ||
| 4369 | + CleanupSubTransaction(); | ||
| 4370 | + s = CurrentTransactionState; | ||
| 4371 | + subTransactionCounter++; | ||
| 4372 | + } while (s->blockState == TBLOCK_SUBINPROGRESS); | ||
| 4373 | + | ||
| 4374 | + Assert(subTransactionCounter == u_sess->SPI_cxt.portal_stp_exception_counter); | ||
| 4375 | + if (s->state == TRANS_START) { | ||
| 4376 | + s->state = TRANS_INPROGRESS; | ||
| 4377 | + } | ||
| 4378 | + | ||
| 4379 | + AbortTransaction(false, stpRollback); | ||
| 4380 | + CleanupTransaction(); | ||
| 4381 | + s->blockState = TBLOCK_DEFAULT; | ||
| 4382 | + } else { | ||
| 4383 | + AbortSubTransaction(); | ||
| 4384 | + s->blockState = TBLOCK_SUBABORT; | ||
| 4385 | + } | ||
| 4386 | + | ||
| 4317 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { | 4387 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { |
| 4318 | ereport(WARNING, | 4388 | ereport(WARNING, |
| 4319 | (errmsg( | 4389 | (errmsg( |
| @@ -4332,7 +4402,7 @@ void AbortCurrentTransaction(void) | |||
| 4332 | case TBLOCK_SUBCOMMIT: | 4402 | case TBLOCK_SUBCOMMIT: |
| 4333 | case TBLOCK_SUBABORT_PENDING: | 4403 | case TBLOCK_SUBABORT_PENDING: |
| 4334 | case TBLOCK_SUBRESTART: | 4404 | case TBLOCK_SUBRESTART: |
| 4335 | - AbortSubTransaction(); | 4405 | + AbortSubTransaction(stpRollback); |
| 4336 | CleanupSubTransaction(); | 4406 | CleanupSubTransaction(); |
| 4337 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { | 4407 | if (t_thrd.xact_cxt.handlesDestroyedInCancelQuery) { |
| 4338 | ereport(WARNING, | 4408 | ereport(WARNING, |
| @@ -4347,7 +4417,7 @@ void AbortCurrentTransaction(void) | |||
| 4347 | case TBLOCK_SUBABORT_END: | 4417 | case TBLOCK_SUBABORT_END: |
| 4348 | case TBLOCK_SUBABORT_RESTART: | 4418 | case TBLOCK_SUBABORT_RESTART: |
| 4349 | CleanupSubTransaction(); | 4419 | CleanupSubTransaction(); |
| 4350 | - AbortCurrentTransaction(); | 4420 | + AbortCurrentTransaction(stpRollback); |
| 4351 | break; | 4421 | break; |
| 4352 | default: | 4422 | default: |
| 4353 | ereport(FATAL, | 4423 | ereport(FATAL, |
| @@ -5473,7 +5543,7 @@ void RollbackAndReleaseCurrentSubTransaction(void) | |||
| 5473 | 5543 | ||
| 5474 | /* Abort the current subtransaction, if needed. */ | 5544 | /* Abort the current subtransaction, if needed. */ |
| 5475 | if (s->blockState == TBLOCK_SUBINPROGRESS) { | 5545 | if (s->blockState == TBLOCK_SUBINPROGRESS) { |
| 5476 | - AbortSubTransaction(); | 5546 | + AbortSubTransaction(false); |
| 5477 | } | 5547 | } |
| 5478 | 5548 | ||
| 5479 | /* And clean it up, too */ | 5549 | /* And clean it up, too */ |
| @@ -5586,7 +5656,7 @@ void AbortOutOfAnyTransaction(bool reserve_topxact_abort) | |||
| 5586 | case TBLOCK_SUBCOMMIT: | 5656 | case TBLOCK_SUBCOMMIT: |
| 5587 | case TBLOCK_SUBABORT_PENDING: | 5657 | case TBLOCK_SUBABORT_PENDING: |
| 5588 | case TBLOCK_SUBRESTART: | 5658 | case TBLOCK_SUBRESTART: |
| 5589 | - AbortSubTransaction(); | 5659 | + AbortSubTransaction(false); |
| 5590 | CleanupSubTransaction(); | 5660 | CleanupSubTransaction(); |
| 5591 | s = CurrentTransactionState; /* changed by pop */ | 5661 | s = CurrentTransactionState; /* changed by pop */ |
| 5592 | break; | 5662 | break; |
| @@ -5627,6 +5697,10 @@ bool IsTransactionBlock(void) | |||
| 5627 | { | 5697 | { |
| 5628 | TransactionState s = CurrentTransactionState; | 5698 | TransactionState s = CurrentTransactionState; |
| 5629 | 5699 | ||
| 5700 | + if (u_sess->SPI_cxt.portal_stp_exception_counter > 0 && s->blockState == TBLOCK_SUBINPROGRESS) { | ||
| 5701 | + return false; | ||
| 5702 | + } | ||
| 5703 | + | ||
| 5630 | if (s->blockState == TBLOCK_DEFAULT || s->blockState == TBLOCK_STARTED) { | 5704 | if (s->blockState == TBLOCK_DEFAULT || s->blockState == TBLOCK_STARTED) { |
| 5631 | return false; | 5705 | return false; |
| 5632 | } | 5706 | } |
| @@ -5770,7 +5844,7 @@ static void StartSubTransaction(void) | |||
| 5770 | * The caller has to make sure to always reassign CurrentTransactionState | 5844 | * The caller has to make sure to always reassign CurrentTransactionState |
| 5771 | * if it has a local pointer to it after calling this function. | 5845 | * if it has a local pointer to it after calling this function. |
| 5772 | */ | 5846 | */ |
| 5773 | -static void CommitSubTransaction(void) | 5847 | +static void CommitSubTransaction(bool stpCommit) |
| 5774 | { | 5848 | { |
| 5775 | TransactionState s = CurrentTransactionState; | 5849 | TransactionState s = CurrentTransactionState; |
| 5776 | 5850 | ||
| @@ -5862,14 +5936,27 @@ static void CommitSubTransaction(void) | |||
| 5862 | XactLockTableDelete(s->transactionId); | 5936 | XactLockTableDelete(s->transactionId); |
| 5863 | } | 5937 | } |
| 5864 | 5938 | ||
| 5939 | + /* | ||
| 5940 | + * When commit within nedted store procedure, it will create a plan cache. | ||
| 5941 | + * During commit time, need to clean up those plan cache. | ||
| 5942 | + */ | ||
| 5943 | + if (stpCommit) { | ||
| 5944 | + ResourceOwnerDecrementNPlanRefs(t_thrd.utils_cxt.CurrentResourceOwner, true); | ||
| 5945 | + ResourceOwnerDecrementNsnapshots(t_thrd.utils_cxt.CurrentResourceOwner, NULL); | ||
| 5946 | + } | ||
| 5947 | + | ||
| 5865 | /* Other locks should get transferred to their parent resource owner. */ | 5948 | /* Other locks should get transferred to their parent resource owner. */ |
| 5866 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_LOCKS, true, false); | 5949 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_LOCKS, true, false); |
| 5867 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_AFTER_LOCKS, true, false); | 5950 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_AFTER_LOCKS, true, false); |
| 5868 | 5951 | ||
| 5869 | AtEOXact_GUC(true, s->gucNestLevel); | 5952 | AtEOXact_GUC(true, s->gucNestLevel); |
| 5870 | - AtEOSubXact_SPI(true, s->subTransactionId); | 5953 | + if (!stpCommit) { |
| 5954 | + AtEOSubXact_SPI(true, s->subTransactionId, false, stpCommit); | ||
| 5955 | + } | ||
| 5871 | AtEOSubXact_on_commit_actions(true, s->subTransactionId, s->parent->subTransactionId); | 5956 | AtEOSubXact_on_commit_actions(true, s->subTransactionId, s->parent->subTransactionId); |
| 5872 | - AtEOSubXact_Namespace(true, s->subTransactionId, s->parent->subTransactionId); | 5957 | + if (!stpCommit) { |
| 5958 | + AtEOSubXact_Namespace(true, s->subTransactionId, s->parent->subTransactionId); | ||
| 5959 | + } | ||
| 5873 | AtEOSubXact_Files(true, s->subTransactionId, s->parent->subTransactionId); | 5960 | AtEOSubXact_Files(true, s->subTransactionId, s->parent->subTransactionId); |
| 5874 | AtEOSubXact_HashTables(true, s->nestingLevel); | 5961 | AtEOSubXact_HashTables(true, s->nestingLevel); |
| 5875 | AtEOSubXact_PgStat(true, s->nestingLevel); | 5962 | AtEOSubXact_PgStat(true, s->nestingLevel); |
| @@ -5895,7 +5982,7 @@ static void CommitSubTransaction(void) | |||
| 5895 | PopTransaction(); | 5982 | PopTransaction(); |
| 5896 | } | 5983 | } |
| 5897 | 5984 | ||
| 5898 | -static void AbortSubTransaction(void) | 5985 | +static void AbortSubTransaction(bool stpRollback) |
| 5899 | { | 5986 | { |
| 5900 | TransactionState s = CurrentTransactionState; | 5987 | TransactionState s = CurrentTransactionState; |
| 5901 | t_thrd.xact_cxt.bInAbortTransaction = true; | 5988 | t_thrd.xact_cxt.bInAbortTransaction = true; |
| @@ -6045,9 +6132,11 @@ static void AbortSubTransaction(void) | |||
| 6045 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false); | 6132 | ResourceOwnerRelease(s->curTransactionOwner, RESOURCE_RELEASE_AFTER_LOCKS, false, false); |
| 6046 | 6133 | ||
| 6047 | AtEOXact_GUC(false, s->gucNestLevel); | 6134 | AtEOXact_GUC(false, s->gucNestLevel); |
| 6048 | - AtEOSubXact_SPI(false, s->subTransactionId); | 6135 | + AtEOSubXact_SPI(false, s->subTransactionId, stpRollback, false); |
| 6049 | AtEOSubXact_on_commit_actions(false, s->subTransactionId, s->parent->subTransactionId); | 6136 | AtEOSubXact_on_commit_actions(false, s->subTransactionId, s->parent->subTransactionId); |
| 6050 | - AtEOSubXact_Namespace(false, s->subTransactionId, s->parent->subTransactionId); | 6137 | + if (!stpRollback) { |
| 6138 | + AtEOSubXact_Namespace(false, s->subTransactionId, s->parent->subTransactionId); | ||
| 6139 | + } | ||
| 6051 | AtEOSubXact_Files(false, s->subTransactionId, s->parent->subTransactionId); | 6140 | AtEOSubXact_Files(false, s->subTransactionId, s->parent->subTransactionId); |
| 6052 | AtEOSubXact_HashTables(false, s->nestingLevel); | 6141 | AtEOSubXact_HashTables(false, s->nestingLevel); |
| 6053 | AtEOSubXact_PgStat(false, s->nestingLevel); | 6142 | AtEOSubXact_PgStat(false, s->nestingLevel); |
| @@ -6217,6 +6306,7 @@ static void PopTransaction(void) | |||
| 6217 | /* Ditto for ResourceOwner links */ | 6306 | /* Ditto for ResourceOwner links */ |
| 6218 | t_thrd.utils_cxt.CurTransactionResourceOwner = s->parent->curTransactionOwner; | 6307 | t_thrd.utils_cxt.CurTransactionResourceOwner = s->parent->curTransactionOwner; |
| 6219 | t_thrd.utils_cxt.CurrentResourceOwner = s->parent->curTransactionOwner; | 6308 | t_thrd.utils_cxt.CurrentResourceOwner = s->parent->curTransactionOwner; |
| 6309 | + t_thrd.xact_cxt.currentSubTransactionId = s->parent->subTransactionId; | ||
| 6220 | 6310 | ||
| 6221 | /* Free the old child structure */ | 6311 | /* Free the old child structure */ |
| 6222 | if (s->name) { | 6312 | if (s->name) { |
| @@ -311,12 +311,12 @@ extern void CopyTransactionIdLoggedIfAny(TransactionState state); | |||
| 311 | extern bool TransactionIdIsCurrentTransactionId(TransactionId xid); | 311 | extern bool TransactionIdIsCurrentTransactionId(TransactionId xid); |
| 312 | extern void CommandCounterIncrement(void); | 312 | extern void CommandCounterIncrement(void); |
| 313 | extern void ForceSyncCommit(void); | 313 | extern void ForceSyncCommit(void); |
| 314 | -extern void StartTransactionCommand(void); | 314 | +extern void StartTransactionCommand(bool stpRollback = false); |
| 315 | -extern void CommitTransactionCommand(void); | 315 | +extern void CommitTransactionCommand(bool stpCommit = false); |
| 316 | 316 | ||
| 317 | extern void AbortCurrentTransactionOnce(void); | 317 | extern void AbortCurrentTransactionOnce(void); |
| 318 | 318 | ||
| 319 | -extern void AbortCurrentTransaction(void); | 319 | +extern void AbortCurrentTransaction(bool stpRollback = false); |
| 320 | extern void BeginTransactionBlock(void); | 320 | extern void BeginTransactionBlock(void); |
| 321 | extern bool EndTransactionBlock(void); | 321 | extern bool EndTransactionBlock(void); |
| 322 | extern bool PrepareTransactionBlock(const char* gid); | 322 | extern bool PrepareTransactionBlock(const char* gid); |
| @@ -52,7 +52,7 @@ extern void CreateCast(CreateCastStmt* stmt); | |||
| 52 | extern void DropCastById(Oid castOid); | 52 | extern void DropCastById(Oid castOid); |
| 53 | extern void AlterFunctionNamespace(List* name, List* argtypes, bool isagg, const char* newschema); | 53 | extern void AlterFunctionNamespace(List* name, List* argtypes, bool isagg, const char* newschema); |
| 54 | extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); | 54 | extern Oid AlterFunctionNamespace_oid(Oid procOid, Oid nspOid); |
| 55 | -extern void ExecuteDoStmt(const DoStmt* stmt); | 55 | +extern void ExecuteDoStmt(const DoStmt* stmt, bool atomic); |
| 56 | extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); | 56 | extern Oid get_cast_oid(Oid sourcetypeid, Oid targettypeid, bool missing_ok); |
| 57 | 57 | ||
| 58 | /* commands/operatorcmds.c */ | 58 | /* commands/operatorcmds.c */ |
| @@ -56,12 +56,15 @@ typedef struct _SPI_plan* SPIPlanPtr; | |||
| 56 | 56 | ||
| 57 | 57 | ||
| 58 | 58 | ||
| 59 | + | ||
| 59 | 60 | ||
| 60 | extern THR_LOCAL PGDLLIMPORT uint32 SPI_processed; | 61 | extern THR_LOCAL PGDLLIMPORT uint32 SPI_processed; |
| 61 | extern THR_LOCAL PGDLLIMPORT SPITupleTable* SPI_tuptable; | 62 | extern THR_LOCAL PGDLLIMPORT SPITupleTable* SPI_tuptable; |
| 62 | extern THR_LOCAL PGDLLIMPORT int SPI_result; | 63 | extern THR_LOCAL PGDLLIMPORT int SPI_result; |
| 63 | 64 | ||
| 64 | extern int SPI_connect(CommandDest dest = DestSPI, void (*spiCallbackfn)(void*) = NULL, void* clientData = NULL); | 65 | extern int SPI_connect(CommandDest dest = DestSPI, void (*spiCallbackfn)(void*) = NULL, void* clientData = NULL); |
| 66 | +extern int SPI_connect_ext(CommandDest dest = DestSPI, void (*spiCallbackfn)(void*) = NULL, | ||
| 67 | + void* clientData = NULL, int options =0); | ||
| 65 | extern int SPI_finish(void); | 68 | extern int SPI_finish(void); |
| 66 | extern void SPI_push(void); | 69 | extern void SPI_push(void); |
| 67 | extern void SPI_pop(void); | 70 | extern void SPI_pop(void); |
| @@ -123,8 +126,13 @@ extern void SPI_scroll_cursor_fetch(Portal, FetchDirection direction, long count | |||
| 123 | extern void SPI_scroll_cursor_move(Portal, FetchDirection direction, long count); | 126 | extern void SPI_scroll_cursor_move(Portal, FetchDirection direction, long count); |
| 124 | extern void SPI_cursor_close(Portal portal); | 127 | extern void SPI_cursor_close(Portal portal); |
| 125 | 128 | ||
| 126 | -extern void AtEOXact_SPI(bool isCommit); | 129 | +extern void SPI_start_transaction(void); |
| 127 | -extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid); | 130 | +extern void SPI_commit(void); |
| 131 | +extern void SPI_rollback(void); | ||
| 132 | +extern void SPICleanup(void); | ||
| 133 | + | ||
| 134 | +extern void AtEOXact_SPI(bool isCommit, bool stpRollback, bool stpCommit); | ||
| 135 | +extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid, bool stpRollback, bool stpCommit); | ||
| 128 | extern DestReceiver* createAnalyzeSPIDestReceiver(CommandDest dest); | 136 | extern DestReceiver* createAnalyzeSPIDestReceiver(CommandDest dest); |
| 129 | /* SPI execution helpers */ | 137 | /* SPI execution helpers */ |
| 130 | extern void spi_exec_with_callback(CommandDest dest, const char* src, bool read_only, long tcount, bool direct_call, | 138 | extern void spi_exec_with_callback(CommandDest dest, const char* src, bool read_only, long tcount, bool direct_call, |
| @@ -28,6 +28,11 @@ typedef struct _SPI_connection { | |||
| 28 | MemoryContext savedcxt; /* context of SPI_connect's caller */ | 28 | MemoryContext savedcxt; /* context of SPI_connect's caller */ |
| 29 | SubTransactionId connectSubid; /* ID of connecting subtransaction */ | 29 | SubTransactionId connectSubid; /* ID of connecting subtransaction */ |
| 30 | CommandDest dest; /* identify which is the orientated caller of spi interface, analyze or normal */ | 30 | CommandDest dest; /* identify which is the orientated caller of spi interface, analyze or normal */ |
| 31 | + | ||
| 32 | + /* transaction management suppoort */ | ||
| 33 | + bool atomic; /* atomic execution context, does not allow transactions */ | ||
| 34 | + bool internal_xact; /* SPI-managed transaction boundary, skip cleanup */ | ||
| 35 | + | ||
| 31 | void* clientData; /* argument to call back function */ | 36 | void* clientData; /* argument to call back function */ |
| 32 | void (*spiCallback)(void*); /* callback for process received data. */ | 37 | void (*spiCallback)(void*); /* callback for process received data. */ |
| 33 | } _SPI_connection; | 38 | } _SPI_connection; |
| @@ -183,6 +183,14 @@ typedef struct knl_u_SPI_context { | |||
| 183 | struct _SPI_connection* _stack; | 183 | struct _SPI_connection* _stack; |
| 184 | 184 | ||
| 185 | struct _SPI_connection* _current; | 185 | struct _SPI_connection* _current; |
| 186 | + | ||
| 187 | + bool is_toplevel_stp; | ||
| 188 | + | ||
| 189 | + bool is_stp; | ||
| 190 | + | ||
| 191 | + bool is_proconfig_set; | ||
| 192 | + | ||
| 193 | + int portal_stp_exception_counter; | ||
| 186 | } knl_u_SPI_context; | 194 | } knl_u_SPI_context; |
| 187 | 195 | ||
| 188 | typedef struct knl_u_index_context { | 196 | typedef struct knl_u_index_context { |
| @@ -1578,6 +1578,7 @@ typedef struct knl_t_utils_context { | |||
| 1578 | struct ResourceOwnerData* CurrentResourceOwner; | 1578 | struct ResourceOwnerData* CurrentResourceOwner; |
| 1579 | struct ResourceOwnerData* CurTransactionResourceOwner; | 1579 | struct ResourceOwnerData* CurTransactionResourceOwner; |
| 1580 | struct ResourceOwnerData* TopTransactionResourceOwner; | 1580 | struct ResourceOwnerData* TopTransactionResourceOwner; |
| 1581 | + struct ResourceOwnerData* StpSavedResourceOwner; | ||
| 1581 | struct ResourceReleaseCallbackItem* ResourceRelease_callbacks; | 1582 | struct ResourceReleaseCallbackItem* ResourceRelease_callbacks; |
| 1582 | bool SortColumnOptimize; | 1583 | bool SortColumnOptimize; |
| 1583 | struct RelationData* pRelatedRel; | 1584 | struct RelationData* pRelatedRel; |
| @@ -1734,6 +1734,7 @@ typedef struct FunctionScanState { | |||
| 1734 | TupleDesc tupdesc; | 1734 | TupleDesc tupdesc; |
| 1735 | Tuplestorestate* tuplestorestate; | 1735 | Tuplestorestate* tuplestorestate; |
| 1736 | ExprState* funcexpr; | 1736 | ExprState* funcexpr; |
| 1737 | + bool atomic; | ||
| 1737 | } FunctionScanState; | 1738 | } FunctionScanState; |
| 1738 | 1739 | ||
| 1739 | /* ---------------- | 1740 | /* ---------------- |
| @@ -2683,8 +2683,14 @@ typedef struct InlineCodeBlock { | |||
| 2683 | char* source_text; /* source text of anonymous code block */ | 2683 | char* source_text; /* source text of anonymous code block */ |
| 2684 | Oid langOid; /* OID of selected language */ | 2684 | Oid langOid; /* OID of selected language */ |
| 2685 | bool langIsTrusted; /* trusted property of the language */ | 2685 | bool langIsTrusted; /* trusted property of the language */ |
| 2686 | + bool atomic; /* atomic execution context */ | ||
| 2686 | } InlineCodeBlock; | 2687 | } InlineCodeBlock; |
| 2687 | 2688 | ||
| 2689 | +typedef struct CallContext { | ||
| 2690 | + NodeTag type; | ||
| 2691 | + bool atomic; | ||
| 2692 | +} CallContext; | ||
| 2693 | + | ||
| 2688 | /* ---------------------- | 2694 | /* ---------------------- |
| 2689 | * Alter Object Rename Statement | 2695 | * Alter Object Rename Statement |
| 2690 | * ---------------------- | 2696 | * ---------------------- |
| @@ -145,6 +145,7 @@ typedef struct PortalData { | |||
| 145 | /* Status data */ | 145 | /* Status data */ |
| 146 | PortalStatus status; /* see above */ | 146 | PortalStatus status; /* see above */ |
| 147 | bool portalPinned; /* a pinned portal can't be dropped */ | 147 | bool portalPinned; /* a pinned portal can't be dropped */ |
| 148 | + bool autoHeld; /* was automatically converted from pinned to held */ | ||
| 148 | 149 | ||
| 149 | /* If not NULL, Executor is active; call ExecutorEnd eventually: */ | 150 | /* If not NULL, Executor is active; call ExecutorEnd eventually: */ |
| 150 | QueryDesc* queryDesc; /* info needed for executor invocation */ | 151 | QueryDesc* queryDesc; /* info needed for executor invocation */ |
| @@ -206,9 +207,10 @@ typedef struct PortalData { | |||
| 206 | 207 | ||
| 207 | /* Prototypes for functions in utils/mmgr/portalmem.c */ | 208 | /* Prototypes for functions in utils/mmgr/portalmem.c */ |
| 208 | extern void EnablePortalManager(void); | 209 | extern void EnablePortalManager(void); |
| 209 | -extern bool PreCommit_Portals(bool isPrepare); | 210 | +extern bool PreCommit_Portals(bool isPrepare, bool stpCommit); |
| 210 | -extern void AtAbort_Portals(void); | 211 | +extern void AtAbort_Portals(bool stpRollback); |
| 211 | extern void AtCleanup_Portals(void); | 212 | extern void AtCleanup_Portals(void); |
| 213 | +extern void PortalErrorCleanup(void); | ||
| 212 | extern void AtSubCommit_Portals(SubTransactionId mySubid, SubTransactionId parentSubid, ResourceOwner parentXactOwner); | 214 | extern void AtSubCommit_Portals(SubTransactionId mySubid, SubTransactionId parentSubid, ResourceOwner parentXactOwner); |
| 213 | extern void AtSubAbort_Portals( | 215 | extern void AtSubAbort_Portals( |
| 214 | SubTransactionId mySubid, SubTransactionId parentSubid, ResourceOwner myXactOwner, ResourceOwner parentXactOwner); | 216 | SubTransactionId mySubid, SubTransactionId parentSubid, ResourceOwner myXactOwner, ResourceOwner parentXactOwner); |
| @@ -229,5 +231,6 @@ extern void PortalCreateHoldStore(Portal portal); | |||
| 229 | extern void PortalHashTableDeleteAll(void); | 231 | extern void PortalHashTableDeleteAll(void); |
| 230 | extern bool ThereAreNoReadyPortals(void); | 232 | extern bool ThereAreNoReadyPortals(void); |
| 231 | extern void ResetPortalCursor(SubTransactionId mySubid, Oid funOid, int funUseCount); | 233 | extern void ResetPortalCursor(SubTransactionId mySubid, Oid funOid, int funUseCount); |
| 234 | +extern void HoldPinnedPortals(void); | ||
| 232 | 235 | ||
| 233 | 236 | ||
| @@ -63,6 +63,9 @@ extern ResourceOwner ResourceOwnerCreate(ResourceOwner parent, const char* name) | |||
| 63 | extern void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); | 63 | extern void ResourceOwnerRelease(ResourceOwner owner, ResourceReleasePhase phase, bool isCommit, bool isTopLevel); |
| 64 | extern void ResourceOwnerDelete(ResourceOwner owner); | 64 | extern void ResourceOwnerDelete(ResourceOwner owner); |
| 65 | extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); | 65 | extern ResourceOwner ResourceOwnerGetParent(ResourceOwner owner); |
| 66 | +extern ResourceOwner ResourceOwnerGetNextChild(ResourceOwner owner); | ||
| 67 | +extern ResourceOwner ResourceOwnerGetFirstChild(ResourceOwner owner); | ||
| 68 | +extern const char* ResourceOwnerGetName(ResourceOwner owner); | ||
| 66 | extern void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent); | 69 | extern void ResourceOwnerNewParent(ResourceOwner owner, ResourceOwner newparent); |
| 67 | extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void* arg); | 70 | extern void RegisterResourceReleaseCallback(ResourceReleaseCallback callback, void* arg); |
| 68 | extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void* arg); | 71 | extern void UnregisterResourceReleaseCallback(ResourceReleaseCallback callback, void* arg); |
| @@ -116,6 +119,8 @@ extern void ResourceOwnerForgetTupleDesc(ResourceOwner owner, const TupleDesc tu | |||
| 116 | extern void ResourceOwnerEnlargeSnapshots(ResourceOwner owner); | 119 | extern void ResourceOwnerEnlargeSnapshots(ResourceOwner owner); |
| 117 | extern void ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot); | 120 | extern void ResourceOwnerRememberSnapshot(ResourceOwner owner, Snapshot snapshot); |
| 118 | extern void ResourceOwnerForgetSnapshot(ResourceOwner owner, const Snapshot snapshot); | 121 | extern void ResourceOwnerForgetSnapshot(ResourceOwner owner, const Snapshot snapshot); |
| 122 | +extern void ResourceOwnerDecrementNsnapshots(ResourceOwner owner, void* queryDesc); | ||
| 123 | +extern void ResourceOwnerDecrementNPlanRefs(ResourceOwner owner, bool useResOwner); | ||
| 119 | 124 | ||
| 120 | /* support for temporary file management */ | 125 | /* support for temporary file management */ |
| 121 | extern void ResourceOwnerEnlargeFiles(ResourceOwner owner); | 126 | extern void ResourceOwnerEnlargeFiles(ResourceOwner owner); |
| @@ -0,0 +1,379 @@ | |||
| 1 | +CREATE TABLE test1 (a int, b text); | ||
| 2 | +CREATE PROCEDURE transaction_test1() | ||
| 3 | +AS | ||
| 4 | +BEGIN | ||
| 5 | + FOR i IN 0..9 LOOP | ||
| 6 | + INSERT INTO test1 (a) VALUES (i); | ||
| 7 | + IF i % 2 = 0 THEN | ||
| 8 | + COMMIT; | ||
| 9 | + ELSE | ||
| 10 | + ROLLBACK; | ||
| 11 | + END IF; | ||
| 12 | + END LOOP; | ||
| 13 | +END; | ||
| 14 | +/ | ||
| 15 | +CALL transaction_test1(); | ||
| 16 | + transaction_test1 | ||
| 17 | +------------------- | ||
| 18 | + | ||
| 19 | +(1 row) | ||
| 20 | + | ||
| 21 | +SELECT * FROM test1; | ||
| 22 | + a | b | ||
| 23 | +---+--- | ||
| 24 | + 0 | | ||
| 25 | + 2 | | ||
| 26 | + 4 | | ||
| 27 | + 6 | | ||
| 28 | + 8 | | ||
| 29 | +(5 rows) | ||
| 30 | + | ||
| 31 | +TRUNCATE test1; | ||
| 32 | +DO | ||
| 33 | +LANGUAGE plpgsql | ||
| 34 | +$$ | ||
| 35 | +BEGIN | ||
| 36 | + FOR i IN 0..9 LOOP | ||
| 37 | + INSERT INTO test1 (a) VALUES (i); | ||
| 38 | + IF i % 2 = 0 THEN | ||
| 39 | + COMMIT; | ||
| 40 | + ELSE | ||
| 41 | + ROLLBACK; | ||
| 42 | + END IF; | ||
| 43 | + END LOOP; | ||
| 44 | +END | ||
| 45 | +$$; | ||
| 46 | +SELECT * FROM test1; | ||
| 47 | + a | b | ||
| 48 | +---+--- | ||
| 49 | + 0 | | ||
| 50 | + 2 | | ||
| 51 | + 4 | | ||
| 52 | + 6 | | ||
| 53 | + 8 | | ||
| 54 | +(5 rows) | ||
| 55 | + | ||
| 56 | +-- transaction commands not allowed when called in transaction block | ||
| 57 | +START TRANSACTION; | ||
| 58 | +CALL transaction_test1(); | ||
| 59 | +ERROR: invalid transaction termination | ||
| 60 | +CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT | ||
| 61 | +COMMIT; | ||
| 62 | +START TRANSACTION; | ||
| 63 | +DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$; | ||
| 64 | +ERROR: invalid transaction termination | ||
| 65 | +CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT | ||
| 66 | +COMMIT; | ||
| 67 | +TRUNCATE test1; | ||
| 68 | +-- not allowed in a function | ||
| 69 | +CREATE FUNCTION transaction_test2() RETURNS int | ||
| 70 | +LANGUAGE plpgsql | ||
| 71 | +AS $$ | ||
| 72 | +BEGIN | ||
| 73 | + FOR i IN 0..9 LOOP | ||
| 74 | + INSERT INTO test1 (a) VALUES (i); | ||
| 75 | + IF i % 2 = 0 THEN | ||
| 76 | + COMMIT; | ||
| 77 | + ELSE | ||
| 78 | + ROLLBACK; | ||
| 79 | + END IF; | ||
| 80 | + END LOOP; | ||
| 81 | + RETURN 1; | ||
| 82 | +END | ||
| 83 | +$$; | ||
| 84 | +SELECT transaction_test2(); | ||
| 85 | +ERROR: cannot commit within function | ||
| 86 | +CONTEXT: PL/pgSQL function transaction_test2() line 6 at COMMIT | ||
| 87 | +referenced column: transaction_test2 | ||
| 88 | +SELECT * FROM test1; | ||
| 89 | + a | b | ||
| 90 | +---+--- | ||
| 91 | +(0 rows) | ||
| 92 | + | ||
| 93 | +-- also not allowed if procedure is called from a function | ||
| 94 | +CREATE FUNCTION transaction_test3() RETURNS int | ||
| 95 | +LANGUAGE plpgsql | ||
| 96 | +AS $$ | ||
| 97 | +BEGIN | ||
| 98 | + CALL transaction_test1(); | ||
| 99 | + RETURN 1; | ||
| 100 | +END; | ||
| 101 | +$$; | ||
| 102 | +SELECT transaction_test3(); | ||
| 103 | +ERROR: cannot commit within function | ||
| 104 | +CONTEXT: PL/pgSQL function transaction_test1() line 6 at COMMIT | ||
| 105 | +SQL statement "CALL transaction_test1()" | ||
| 106 | +PL/pgSQL function transaction_test3() line 3 at SQL statement | ||
| 107 | +referenced column: transaction_test3 | ||
| 108 | +SELECT * FROM test1; | ||
| 109 | + a | b | ||
| 110 | +---+--- | ||
| 111 | +(0 rows) | ||
| 112 | + | ||
| 113 | +-- DO block inside function | ||
| 114 | +CREATE FUNCTION transaction_test4() RETURNS int | ||
| 115 | +LANGUAGE plpgsql | ||
| 116 | +AS $$ | ||
| 117 | +BEGIN | ||
| 118 | + EXECUTE 'DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$'; | ||
| 119 | + RETURN 1; | ||
| 120 | +END; | ||
| 121 | +$$; | ||
| 122 | +SELECT transaction_test4(); | ||
| 123 | +ERROR: cannot commit within function | ||
| 124 | +CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT | ||
| 125 | +SQL statement "DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$" | ||
| 126 | +PL/pgSQL function transaction_test4() line 3 at EXECUTE statement | ||
| 127 | +referenced column: transaction_test4 | ||
| 128 | +-- proconfig settings currently disallow transaction statements | ||
| 129 | +CREATE PROCEDURE transaction_test5() | ||
| 130 | +SET work_mem = 555 | ||
| 131 | +AS | ||
| 132 | +BEGIN | ||
| 133 | + COMMIT; | ||
| 134 | +END; | ||
| 135 | +/ | ||
| 136 | +CALL transaction_test5(); | ||
| 137 | +ERROR: invalid transaction termination | ||
| 138 | +CONTEXT: PL/pgSQL function transaction_test5() line 3 at COMMIT | ||
| 139 | +-- commit inside cursor loop | ||
| 140 | +CREATE TABLE test2 (x int); | ||
| 141 | +INSERT INTO test2 VALUES (0), (1), (2), (3), (4); | ||
| 142 | +TRUNCATE test1; | ||
| 143 | +DO LANGUAGE plpgsql $$ | ||
| 144 | +DECLARE | ||
| 145 | + r RECORD; | ||
| 146 | +BEGIN | ||
| 147 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 148 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 149 | + COMMIT; | ||
| 150 | + END LOOP; | ||
| 151 | +END; | ||
| 152 | +$$; | ||
| 153 | +SELECT * FROM test1; | ||
| 154 | + a | b | ||
| 155 | +---+--- | ||
| 156 | + 0 | | ||
| 157 | + 1 | | ||
| 158 | + 2 | | ||
| 159 | + 3 | | ||
| 160 | + 4 | | ||
| 161 | +(5 rows) | ||
| 162 | + | ||
| 163 | +-- check that this doesn't leak a holdable portal | ||
| 164 | +SELECT * FROM pg_cursors; | ||
| 165 | + name | statement | is_holdable | is_binary | is_scrollable | creation_time | ||
| 166 | +------+-----------+-------------+-----------+---------------+--------------- | ||
| 167 | +(0 rows) | ||
| 168 | + | ||
| 169 | +-- error in cursor loop with commit | ||
| 170 | +TRUNCATE test1; | ||
| 171 | +DO LANGUAGE plpgsql $$ | ||
| 172 | +DECLARE | ||
| 173 | + r RECORD; | ||
| 174 | +BEGIN | ||
| 175 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 176 | + INSERT INTO test1 (a) VALUES (12/(r.x-2)); | ||
| 177 | + COMMIT; | ||
| 178 | + END LOOP; | ||
| 179 | +END; | ||
| 180 | +$$; | ||
| 181 | +ERROR: division by zero | ||
| 182 | +CONTEXT: referenced column: a | ||
| 183 | +SQL statement "INSERT INTO test1 (a) VALUES (12/(r.x-2))" | ||
| 184 | +PL/pgSQL function inline_code_block line 6 at SQL statement | ||
| 185 | +SELECT * FROM test1; | ||
| 186 | + a | b | ||
| 187 | +-----+--- | ||
| 188 | + -6 | | ||
| 189 | + -12 | | ||
| 190 | +(2 rows) | ||
| 191 | + | ||
| 192 | +SELECT * FROM pg_cursors; | ||
| 193 | + name | statement | is_holdable | is_binary | is_scrollable | creation_time | ||
| 194 | +------+-----------+-------------+-----------+---------------+--------------- | ||
| 195 | +(0 rows) | ||
| 196 | + | ||
| 197 | +-- rollback inside cursor loop | ||
| 198 | +TRUNCATE test1; | ||
| 199 | +DO LANGUAGE plpgsql $$ | ||
| 200 | +DECLARE | ||
| 201 | + r RECORD; | ||
| 202 | +BEGIN | ||
| 203 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 204 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 205 | + ROLLBACK; | ||
| 206 | + END LOOP; | ||
| 207 | +END; | ||
| 208 | +$$; | ||
| 209 | +SELECT * FROM test1; | ||
| 210 | + a | b | ||
| 211 | +---+--- | ||
| 212 | +(0 rows) | ||
| 213 | + | ||
| 214 | +SELECT * FROM pg_cursors; | ||
| 215 | + name | statement | is_holdable | is_binary | is_scrollable | creation_time | ||
| 216 | +------+-----------+-------------+-----------+---------------+--------------- | ||
| 217 | +(0 rows) | ||
| 218 | + | ||
| 219 | +-- first commit then rollback inside cursor loop | ||
| 220 | +TRUNCATE test1; | ||
| 221 | +DO LANGUAGE plpgsql $$ | ||
| 222 | +DECLARE | ||
| 223 | + r RECORD; | ||
| 224 | +BEGIN | ||
| 225 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 226 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 227 | + IF r.x % 2 = 0 THEN | ||
| 228 | + COMMIT; | ||
| 229 | + ELSE | ||
| 230 | + ROLLBACK; | ||
| 231 | + END IF; | ||
| 232 | + END LOOP; | ||
| 233 | +END; | ||
| 234 | +$$; | ||
| 235 | +SELECT * FROM test1; | ||
| 236 | + a | b | ||
| 237 | +---+--- | ||
| 238 | + 0 | | ||
| 239 | + 2 | | ||
| 240 | + 4 | | ||
| 241 | +(3 rows) | ||
| 242 | + | ||
| 243 | +SELECT * FROM pg_cursors; | ||
| 244 | + name | statement | is_holdable | is_binary | is_scrollable | creation_time | ||
| 245 | +------+-----------+-------------+-----------+---------------+--------------- | ||
| 246 | +(0 rows) | ||
| 247 | + | ||
| 248 | +-- rollback inside cursor loop | ||
| 249 | +TRUNCATE test1; | ||
| 250 | +DO LANGUAGE plpgsql $$ | ||
| 251 | +DECLARE | ||
| 252 | + r RECORD; | ||
| 253 | +BEGIN | ||
| 254 | + FOR r IN UPDATE test2 SET x = x * 2 RETURNING x LOOP | ||
| 255 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 256 | + ROLLBACK; | ||
| 257 | + END LOOP; | ||
| 258 | +END; | ||
| 259 | +$$; | ||
| 260 | +ERROR: cannot perform transaction commands inside a cursor loop that is not read-only | ||
| 261 | +CONTEXT: PL/pgSQL function inline_code_block line 7 at ROLLBACK | ||
| 262 | +SELECT * FROM test1; | ||
| 263 | + a | b | ||
| 264 | +---+--- | ||
| 265 | +(0 rows) | ||
| 266 | + | ||
| 267 | +SELECT * FROM test2; | ||
| 268 | + x | ||
| 269 | +--- | ||
| 270 | + 0 | ||
| 271 | + 1 | ||
| 272 | + 2 | ||
| 273 | + 3 | ||
| 274 | + 4 | ||
| 275 | +(5 rows) | ||
| 276 | + | ||
| 277 | +SELECT * FROM pg_cursors; | ||
| 278 | + name | statement | is_holdable | is_binary | is_scrollable | creation_time | ||
| 279 | +------+-----------+-------------+-----------+---------------+--------------- | ||
| 280 | +(0 rows) | ||
| 281 | + | ||
| 282 | +-- commit inside block with exception handler | ||
| 283 | +TRUNCATE test1; | ||
| 284 | +DO LANGUAGE plpgsql $$ | ||
| 285 | +BEGIN | ||
| 286 | + BEGIN | ||
| 287 | + INSERT INTO test1 (a) VALUES (1); | ||
| 288 | + COMMIT; | ||
| 289 | + INSERT INTO test1 (a) VALUES (1/0); | ||
| 290 | + COMMIT; | ||
| 291 | + EXCEPTION | ||
| 292 | + WHEN division_by_zero THEN | ||
| 293 | + RAISE NOTICE 'caught division_by_zero'; | ||
| 294 | + END; | ||
| 295 | +END; | ||
| 296 | +$$; | ||
| 297 | +NOTICE: caught division_by_zero | ||
| 298 | +SELECT * FROM test1; | ||
| 299 | + a | b | ||
| 300 | +---+--- | ||
| 301 | + 1 | | ||
| 302 | +(1 row) | ||
| 303 | + | ||
| 304 | +-- rollback inside block with exception handler | ||
| 305 | +TRUNCATE test1; | ||
| 306 | +DO LANGUAGE plpgsql $$ | ||
| 307 | +BEGIN | ||
| 308 | + BEGIN | ||
| 309 | + INSERT INTO test1 (a) VALUES (1); | ||
| 310 | + ROLLBACK; | ||
| 311 | + INSERT INTO test1 (a) VALUES (1/0); | ||
| 312 | + ROLLBACK; | ||
| 313 | + EXCEPTION | ||
| 314 | + WHEN division_by_zero THEN | ||
| 315 | + RAISE NOTICE 'caught division_by_zero'; | ||
| 316 | + END; | ||
| 317 | +END; | ||
| 318 | +$$; | ||
| 319 | +NOTICE: caught division_by_zero | ||
| 320 | +SELECT * FROM test1; | ||
| 321 | + a | b | ||
| 322 | +---+--- | ||
| 323 | +(0 rows) | ||
| 324 | + | ||
| 325 | +-- COMMIT failures | ||
| 326 | +DO LANGUAGE plpgsql $$ | ||
| 327 | +BEGIN | ||
| 328 | + CREATE TABLE test3 (y int UNIQUE DEFERRABLE INITIALLY DEFERRED); | ||
| 329 | + COMMIT; | ||
| 330 | + INSERT INTO test3 (y) VALUES (1); | ||
| 331 | + COMMIT; | ||
| 332 | + INSERT INTO test3 (y) VALUES (1); | ||
| 333 | + INSERT INTO test3 (y) VALUES (2); | ||
| 334 | + COMMIT; | ||
| 335 | + INSERT INTO test3 (y) VALUES (3); -- won't get here | ||
| 336 | +END; | ||
| 337 | +$$; | ||
| 338 | +NOTICE: CREATE TABLE / UNIQUE will create implicit index "test3_y_key" for table "test3" | ||
| 339 | +CONTEXT: SQL statement "CREATE TABLE test3 (y int UNIQUE DEFERRABLE INITIALLY DEFERRED)" | ||
| 340 | +PL/pgSQL function inline_code_block line 3 at SQL statement | ||
| 341 | +ERROR: duplicate key value violates unique constraint "test3_y_key" | ||
| 342 | +DETAIL: Key (y)=(1) already exists. | ||
| 343 | +CONTEXT: PL/pgSQL function inline_code_block line 9 at COMMIT | ||
| 344 | +SELECT * FROM test3; | ||
| 345 | + y | ||
| 346 | +--- | ||
| 347 | + 1 | ||
| 348 | +(1 row) | ||
| 349 | + | ||
| 350 | +DROP TABLE test1; | ||
| 351 | +DROP TABLE test2; | ||
| 352 | +DROP TABLE test3; | ||
| 353 | +-- | ||
| 354 | +CREATE TABLE test1(id int, name varchar(20)); | ||
| 355 | +INSERT INTO test1 values(1, 'bbb'); | ||
| 356 | +CREATE OR REPLACE PROCEDURE PROC_OUT_PARAM_001(P1 OUT INT) | ||
| 357 | +AS | ||
| 358 | +BEGIN | ||
| 359 | +select id into P1 from test1 where name = 'bbb'; | ||
| 360 | +insert into test1 values(P1, 'dddd'); | ||
| 361 | +COMMIT; | ||
| 362 | +insert into test1 values(P1, 'eee'); | ||
| 363 | +ROLLBACK; | ||
| 364 | +END; | ||
| 365 | +/ | ||
| 366 | +DECLARE | ||
| 367 | +V_P1 INT; | ||
| 368 | +BEGIN | ||
| 369 | +PROC_OUT_PARAM_001(V_P1); | ||
| 370 | +END; | ||
| 371 | +/ | ||
| 372 | +SELECT * from test1; | ||
| 373 | + id | name | ||
| 374 | +----+------ | ||
| 375 | + 1 | bbb | ||
| 376 | + 1 | dddd | ||
| 377 | +(2 rows) | ||
| 378 | + | ||
| 379 | +DROP TABLE TEST1; | ||
| @@ -248,7 +248,7 @@ test: subplan_new | |||
| 248 | test: select | 248 | test: select |
| 249 | test: col_subplan_base_1 col_subplan_new | 249 | test: col_subplan_base_1 col_subplan_new |
| 250 | test: join | 250 | test: join |
| 251 | -test: select_into select_distinct subselect_part1 subselect_part2 transactions random btree_index select_distinct_on union gs_aggregate arrays hash_index | 251 | +test: select_into select_distinct subselect_part1 subselect_part2 transactions transactions_control random btree_index select_distinct_on union gs_aggregate arrays hash_index |
| 252 | test: aggregates | 252 | test: aggregates |
| 253 | test: portals_p2 window tsearch temp__6 holdable_cursor col_subplan_base_2 | 253 | test: portals_p2 window tsearch temp__6 holdable_cursor col_subplan_base_2 |
| 254 | 254 | ||
| @@ -0,0 +1,305 @@ | |||
| 1 | +CREATE TABLE test1 (a int, b text); | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +CREATE PROCEDURE transaction_test1() | ||
| 5 | +AS | ||
| 6 | +BEGIN | ||
| 7 | + FOR i IN 0..9 LOOP | ||
| 8 | + INSERT INTO test1 (a) VALUES (i); | ||
| 9 | + IF i % 2 = 0 THEN | ||
| 10 | + COMMIT; | ||
| 11 | + ELSE | ||
| 12 | + ROLLBACK; | ||
| 13 | + END IF; | ||
| 14 | + END LOOP; | ||
| 15 | +END; | ||
| 16 | +/ | ||
| 17 | + | ||
| 18 | +CALL transaction_test1(); | ||
| 19 | + | ||
| 20 | +SELECT * FROM test1; | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +TRUNCATE test1; | ||
| 24 | + | ||
| 25 | +DO | ||
| 26 | +LANGUAGE plpgsql | ||
| 27 | +$$ | ||
| 28 | +BEGIN | ||
| 29 | + FOR i IN 0..9 LOOP | ||
| 30 | + INSERT INTO test1 (a) VALUES (i); | ||
| 31 | + IF i % 2 = 0 THEN | ||
| 32 | + COMMIT; | ||
| 33 | + ELSE | ||
| 34 | + ROLLBACK; | ||
| 35 | + END IF; | ||
| 36 | + END LOOP; | ||
| 37 | +END | ||
| 38 | +$$; | ||
| 39 | + | ||
| 40 | +SELECT * FROM test1; | ||
| 41 | + | ||
| 42 | + | ||
| 43 | +-- transaction commands not allowed when called in transaction block | ||
| 44 | +START TRANSACTION; | ||
| 45 | +CALL transaction_test1(); | ||
| 46 | +COMMIT; | ||
| 47 | + | ||
| 48 | +START TRANSACTION; | ||
| 49 | +DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$; | ||
| 50 | +COMMIT; | ||
| 51 | + | ||
| 52 | + | ||
| 53 | +TRUNCATE test1; | ||
| 54 | + | ||
| 55 | +-- not allowed in a function | ||
| 56 | +CREATE FUNCTION transaction_test2() RETURNS int | ||
| 57 | +LANGUAGE plpgsql | ||
| 58 | +AS $$ | ||
| 59 | +BEGIN | ||
| 60 | + FOR i IN 0..9 LOOP | ||
| 61 | + INSERT INTO test1 (a) VALUES (i); | ||
| 62 | + IF i % 2 = 0 THEN | ||
| 63 | + COMMIT; | ||
| 64 | + ELSE | ||
| 65 | + ROLLBACK; | ||
| 66 | + END IF; | ||
| 67 | + END LOOP; | ||
| 68 | + RETURN 1; | ||
| 69 | +END | ||
| 70 | +$$; | ||
| 71 | + | ||
| 72 | +SELECT transaction_test2(); | ||
| 73 | + | ||
| 74 | +SELECT * FROM test1; | ||
| 75 | + | ||
| 76 | + | ||
| 77 | +-- also not allowed if procedure is called from a function | ||
| 78 | +CREATE FUNCTION transaction_test3() RETURNS int | ||
| 79 | +LANGUAGE plpgsql | ||
| 80 | +AS $$ | ||
| 81 | +BEGIN | ||
| 82 | + CALL transaction_test1(); | ||
| 83 | + RETURN 1; | ||
| 84 | +END; | ||
| 85 | +$$; | ||
| 86 | + | ||
| 87 | +SELECT transaction_test3(); | ||
| 88 | + | ||
| 89 | +SELECT * FROM test1; | ||
| 90 | + | ||
| 91 | + | ||
| 92 | +-- DO block inside function | ||
| 93 | +CREATE FUNCTION transaction_test4() RETURNS int | ||
| 94 | +LANGUAGE plpgsql | ||
| 95 | +AS $$ | ||
| 96 | +BEGIN | ||
| 97 | + EXECUTE 'DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$'; | ||
| 98 | + RETURN 1; | ||
| 99 | +END; | ||
| 100 | +$$; | ||
| 101 | + | ||
| 102 | +SELECT transaction_test4(); | ||
| 103 | + | ||
| 104 | + | ||
| 105 | +-- proconfig settings currently disallow transaction statements | ||
| 106 | +CREATE PROCEDURE transaction_test5() | ||
| 107 | +SET work_mem = 555 | ||
| 108 | +AS | ||
| 109 | +BEGIN | ||
| 110 | + COMMIT; | ||
| 111 | +END; | ||
| 112 | +/ | ||
| 113 | + | ||
| 114 | +CALL transaction_test5(); | ||
| 115 | + | ||
| 116 | + | ||
| 117 | +-- commit inside cursor loop | ||
| 118 | +CREATE TABLE test2 (x int); | ||
| 119 | +INSERT INTO test2 VALUES (0), (1), (2), (3), (4); | ||
| 120 | + | ||
| 121 | +TRUNCATE test1; | ||
| 122 | + | ||
| 123 | +DO LANGUAGE plpgsql $$ | ||
| 124 | +DECLARE | ||
| 125 | + r RECORD; | ||
| 126 | +BEGIN | ||
| 127 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 128 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 129 | + COMMIT; | ||
| 130 | + END LOOP; | ||
| 131 | +END; | ||
| 132 | +$$; | ||
| 133 | + | ||
| 134 | +SELECT * FROM test1; | ||
| 135 | + | ||
| 136 | +-- check that this doesn't leak a holdable portal | ||
| 137 | +SELECT * FROM pg_cursors; | ||
| 138 | + | ||
| 139 | + | ||
| 140 | +-- error in cursor loop with commit | ||
| 141 | +TRUNCATE test1; | ||
| 142 | + | ||
| 143 | +DO LANGUAGE plpgsql $$ | ||
| 144 | +DECLARE | ||
| 145 | + r RECORD; | ||
| 146 | +BEGIN | ||
| 147 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 148 | + INSERT INTO test1 (a) VALUES (12/(r.x-2)); | ||
| 149 | + COMMIT; | ||
| 150 | + END LOOP; | ||
| 151 | +END; | ||
| 152 | +$$; | ||
| 153 | + | ||
| 154 | +SELECT * FROM test1; | ||
| 155 | + | ||
| 156 | +SELECT * FROM pg_cursors; | ||
| 157 | + | ||
| 158 | + | ||
| 159 | +-- rollback inside cursor loop | ||
| 160 | +TRUNCATE test1; | ||
| 161 | + | ||
| 162 | +DO LANGUAGE plpgsql $$ | ||
| 163 | +DECLARE | ||
| 164 | + r RECORD; | ||
| 165 | +BEGIN | ||
| 166 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 167 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 168 | + ROLLBACK; | ||
| 169 | + END LOOP; | ||
| 170 | +END; | ||
| 171 | +$$; | ||
| 172 | + | ||
| 173 | +SELECT * FROM test1; | ||
| 174 | + | ||
| 175 | +SELECT * FROM pg_cursors; | ||
| 176 | + | ||
| 177 | + | ||
| 178 | +-- first commit then rollback inside cursor loop | ||
| 179 | +TRUNCATE test1; | ||
| 180 | + | ||
| 181 | +DO LANGUAGE plpgsql $$ | ||
| 182 | +DECLARE | ||
| 183 | + r RECORD; | ||
| 184 | +BEGIN | ||
| 185 | + FOR r IN SELECT * FROM test2 ORDER BY x LOOP | ||
| 186 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 187 | + IF r.x % 2 = 0 THEN | ||
| 188 | + COMMIT; | ||
| 189 | + ELSE | ||
| 190 | + ROLLBACK; | ||
| 191 | + END IF; | ||
| 192 | + END LOOP; | ||
| 193 | +END; | ||
| 194 | +$$; | ||
| 195 | + | ||
| 196 | +SELECT * FROM test1; | ||
| 197 | + | ||
| 198 | +SELECT * FROM pg_cursors; | ||
| 199 | + | ||
| 200 | + | ||
| 201 | +-- rollback inside cursor loop | ||
| 202 | +TRUNCATE test1; | ||
| 203 | + | ||
| 204 | +DO LANGUAGE plpgsql $$ | ||
| 205 | +DECLARE | ||
| 206 | + r RECORD; | ||
| 207 | +BEGIN | ||
| 208 | + FOR r IN UPDATE test2 SET x = x * 2 RETURNING x LOOP | ||
| 209 | + INSERT INTO test1 (a) VALUES (r.x); | ||
| 210 | + ROLLBACK; | ||
| 211 | + END LOOP; | ||
| 212 | +END; | ||
| 213 | +$$; | ||
| 214 | + | ||
| 215 | +SELECT * FROM test1; | ||
| 216 | +SELECT * FROM test2; | ||
| 217 | + | ||
| 218 | +SELECT * FROM pg_cursors; | ||
| 219 | + | ||
| 220 | +-- commit inside block with exception handler | ||
| 221 | +TRUNCATE test1; | ||
| 222 | + | ||
| 223 | +DO LANGUAGE plpgsql $$ | ||
| 224 | +BEGIN | ||
| 225 | + BEGIN | ||
| 226 | + INSERT INTO test1 (a) VALUES (1); | ||
| 227 | + COMMIT; | ||
| 228 | + INSERT INTO test1 (a) VALUES (1/0); | ||
| 229 | + COMMIT; | ||
| 230 | + EXCEPTION | ||
| 231 | + WHEN division_by_zero THEN | ||
| 232 | + RAISE NOTICE 'caught division_by_zero'; | ||
| 233 | + END; | ||
| 234 | +END; | ||
| 235 | +$$; | ||
| 236 | + | ||
| 237 | +SELECT * FROM test1; | ||
| 238 | + | ||
| 239 | + | ||
| 240 | +-- rollback inside block with exception handler | ||
| 241 | +TRUNCATE test1; | ||
| 242 | + | ||
| 243 | +DO LANGUAGE plpgsql $$ | ||
| 244 | +BEGIN | ||
| 245 | + BEGIN | ||
| 246 | + INSERT INTO test1 (a) VALUES (1); | ||
| 247 | + ROLLBACK; | ||
| 248 | + INSERT INTO test1 (a) VALUES (1/0); | ||
| 249 | + ROLLBACK; | ||
| 250 | + EXCEPTION | ||
| 251 | + WHEN division_by_zero THEN | ||
| 252 | + RAISE NOTICE 'caught division_by_zero'; | ||
| 253 | + END; | ||
| 254 | +END; | ||
| 255 | +$$; | ||
| 256 | + | ||
| 257 | +SELECT * FROM test1; | ||
| 258 | + | ||
| 259 | + | ||
| 260 | +-- COMMIT failures | ||
| 261 | +DO LANGUAGE plpgsql $$ | ||
| 262 | +BEGIN | ||
| 263 | + CREATE TABLE test3 (y int UNIQUE DEFERRABLE INITIALLY DEFERRED); | ||
| 264 | + COMMIT; | ||
| 265 | + INSERT INTO test3 (y) VALUES (1); | ||
| 266 | + COMMIT; | ||
| 267 | + INSERT INTO test3 (y) VALUES (1); | ||
| 268 | + INSERT INTO test3 (y) VALUES (2); | ||
| 269 | + COMMIT; | ||
| 270 | + INSERT INTO test3 (y) VALUES (3); -- won't get here | ||
| 271 | +END; | ||
| 272 | +$$; | ||
| 273 | + | ||
| 274 | +SELECT * FROM test3; | ||
| 275 | + | ||
| 276 | + | ||
| 277 | +DROP TABLE test1; | ||
| 278 | +DROP TABLE test2; | ||
| 279 | +DROP TABLE test3; | ||
| 280 | + | ||
| 281 | +-- | ||
| 282 | +CREATE TABLE test1(id int, name varchar(20)); | ||
| 283 | +INSERT INTO test1 values(1, 'bbb'); | ||
| 284 | + | ||
| 285 | +CREATE OR REPLACE PROCEDURE PROC_OUT_PARAM_001(P1 OUT INT) | ||
| 286 | +AS | ||
| 287 | +BEGIN | ||
| 288 | +select id into P1 from test1 where name = 'bbb'; | ||
| 289 | +insert into test1 values(P1, 'dddd'); | ||
| 290 | +COMMIT; | ||
| 291 | +insert into test1 values(P1, 'eee'); | ||
| 292 | +ROLLBACK; | ||
| 293 | +END; | ||
| 294 | +/ | ||
| 295 | + | ||
| 296 | +DECLARE | ||
| 297 | +V_P1 INT; | ||
| 298 | +BEGIN | ||
| 299 | +PROC_OUT_PARAM_001(V_P1); | ||
| 300 | +END; | ||
| 301 | +/ | ||
| 302 | + | ||
| 303 | +SELECT * from test1; | ||
| 304 | + | ||
| 305 | +DROP TABLE TEST1; | ||