Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h72Ei-0005cQ-W7 for pgsql-hackers@arkaria.postgresql.org; Thu, 21 Mar 2019 18:16:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1h72Eh-0004ll-EP for pgsql-hackers@arkaria.postgresql.org; Thu, 21 Mar 2019 18:16:15 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h72Ef-0004d2-4a for pgsql-hackers@lists.postgresql.org; Thu, 21 Mar 2019 18:16:15 +0000 Received: from out5-smtp.messagingengine.com ([66.111.4.29]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1h72EX-0005kI-09 for pgsql-hackers@postgresql.org; Thu, 21 Mar 2019 18:16:12 +0000 Received: from compute5.internal (compute5.nyi.internal [10.202.2.45]) by mailout.nyi.internal (Postfix) with ESMTP id 2569121A7B; Thu, 21 Mar 2019 14:16:02 -0400 (EDT) Received: from mailfrontend1 ([10.202.2.162]) by compute5.internal (MEProxy); Thu, 21 Mar 2019 14:16:02 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=anarazel.de; h= date:from:to:cc:subject:message-id:references:mime-version :content-type:in-reply-to; s=fm1; bh=lpUUMIjZsB8gRkZ9kzmrLUp8lqr KPuMvKwN6hZSLocI=; b=2XpFTcU/TIRu/3i5CD1dUF1uO8SyG5NKPPnHpqiOzbe a3d0LH0Kklkxs2YJ/okNSJXYHpTaylJp+Ztgw/yY3ajegycBtQ+/uGb8f0xuJ4e1 0nwxlTQOAWut5v5jKboONj0tjLP3bOcNmeAeNHOfUvjbr9E6faqAgqd0jTqXuv56 57DzA5Eq8UsNUkNDJf+8dDwjl9dem84V/CP6ZTd+LKfmRx41LlXCbWJXISCn/Gq3 j7na6anWEFjeR81FOTust6nXjvPH6fTegXpRO8CpTQgG+L/XLIOmDHDV9vjiBkJS 39HsKQzWSNaN/FZq9V+c6Hr6KR1PN81FgvYzDU1RtDg== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:date:from:in-reply-to :message-id:mime-version:references:subject:to:x-me-proxy :x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm2; bh=lpUUMI jZsB8gRkZ9kzmrLUp8lqrKPuMvKwN6hZSLocI=; b=iUtQTA0m/l9ZeLQ3cvWn4y W7w+NWB1rIbQAPoXs0lKpn/W0eD4n0tPZY4RKby9LxppBcuRJr6gjFZ2szntByla bK748nNFWU6ArebgWhIB2EBzEUkc7gIG6Gn2hIoznsXAmPwQ1dSeQB+ASuluU6+P JbBIee8p5PVMsk6Hr89vI4IhoPaY+XGLsSPxIfV2Fx9alMQEcwGgN7WsLWrEHae/ pNc2pia0XxpEDwxJAy98Hqey3T8O/qoNdjn+2dxqBpAGuDOKFq2XU/G0sLW8ZuYl rmEr2Kr0MHV/SkfEP39zbdfW5djMnTUj79954kv3g0KT5S8s4XaaDEGHkVWre3xg == X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgedutddrieelgdelhecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpeffhffvuffkfhggtggujgesmhdtreertddtvdenucfhrhhomheptehnughrvghs ucfhrhgvuhhnugcuoegrnhgurhgvshesrghnrghrrgiivghlrdguvgeqnecukfhppeelke drvddutddrudegtddrudejudenucfrrghrrghmpehmrghilhhfrhhomheprghnughrvghs segrnhgrrhgriigvlhdruggvnecuvehluhhsthgvrhfuihiivgeptd X-ME-Proxy: Received: from intern.anarazel.de (c-98-210-140-171.hsd1.ca.comcast.net [98.210.140.171]) by mail.messagingengine.com (Postfix) with ESMTPA id 6AA4FE4210; Thu, 21 Mar 2019 14:15:59 -0400 (EDT) Date: Thu, 21 Mar 2019 11:15:57 -0700 From: Andres Freund To: Haribabu Kommi Cc: Heikki Linnakangas , Asim R P , Pg Hackers , Ashwin Agrawal , Dmitry Dolgov <9erthalion6@gmail.com>, Alvaro Herrera , Alexander Korotkov Subject: Re: Pluggable Storage - Andres's take Message-ID: <20190321181557.o7sx6ul47t2ojjcm@alap3.anarazel.de> References: <20181127015557.unah7opawwrvr3us@alap3.anarazel.de> <20181211021340.mqaown4njtcgrjvr@alap3.anarazel.de> <20190121030115.unoxypsx3dxosyeg@alap3.anarazel.de> <20190227172931.vxg4kuu7gjcwgz76@alap3.anarazel.de> <20190306070721.dd34u7g7ogtlvf7n@alap3.anarazel.de> <20190309031310.6qjht3gzdrj4iytj@alap3.anarazel.de> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="qw3ipai4e5xhdytn" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --qw3ipai4e5xhdytn Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi, Attached is a version of just the first patch. I'm still updating it, but it's getting closer to commit: - There were no tests testing EPQ interactions with DELETE, and only an accidental test for EPQ in UPDATE with a concurrent DELETE. I've added tests. Plan to commit that ahead of the big change. - I was pretty unhappy about how the EPQ integration looked before, I've changed that now. I still wonder if we should restore EvalPlanQualFetch and move the table_lock_tuple() calls in ExecDelete/Update into it. But it seems like it'd not gain that much, because there's custom surrounding code, and it's not that much code. - I changed heapam_tuple_tuple to return *WouldBlock rather than just the last result. I think that's one of the reason Haribabu had neutered a few asserts. - I moved comments from heapam.h to tableam.h where appropriate - I updated the name of HeapUpdateFailureData to TM_FailureData, HTSU_Result to TM_Result, TM_Results members now properly distinguish between update vs modifications (delete & update). - I separated the HEAP_INSERT_ flags into TABLE_INSERT_* and HEAP_INSERT_ with the latter being a copy of TABLE_INSERT_ with the sole addition of _SPECULATIVE. table_insert_speculative callers now don't specify that anymore. Pending work: - Wondering if table_insert/delete/update should rather be table_tuple_insert etc. Would be a bit more consistent with the callback names, but a bigger departure from existing code. - I'm not yet happy with TableTupleDeleted computation in heapam.c, I want to revise that further - formatting - commit message - a few comments need a bit of polishing (ExecCheckTIDVisible, heapam_tuple_lock) - Rename TableTupleMayBeModified to TableTupleOk, but also probably a s/TableTuple/TableMod/ - I'll probably move TUPLE_LOCK_FLAG_LOCK_* into tableam.h - two more passes through the patch On 2019-03-21 15:07:04 +1100, Haribabu Kommi wrote: > As you are modifying the 0003 patch for modify API's, I went and reviewed > the > existing patch and found couple corrections that are needed, in case if you > are not > taken care of them already. Some I have... > + /* Update the tuple with table oid */ > + slot->tts_tableOid = RelationGetRelid(relation); > + if (slot->tts_tableOid != InvalidOid) > + tuple->t_tableOid = slot->tts_tableOid; > > The setting of slot->tts_tableOid is not required in this function, > After set the check is happening, the above code is present in both > heapam_heap_insert and heapam_heap_insert_speculative. I'm not following? Those functions are independent? > + slot->tts_tableOid = RelationGetRelid(relation); > > In heapam_heap_update, i don't think there is a need to update > slot->tts_tableOid. Why? > + default: > + elog(ERROR, "unrecognized heap_update status: %u", result); > > heap_update --> table_update? > > > + default: > + elog(ERROR, "unrecognized heap_delete status: %u", result); > > same as above? I've fixed that in a number of places. > + /*hari FIXME*/ > + /*Assert(result != HeapTupleUpdated && hufd.traversed);*/ > > Removing the commented codes in both ExecDelete and ExecUpdate functions. I don't think that's the right fix. I've refactored that code significantly now, and restored the assert in a, imo, correct version. > + /**/ > + if (epqreturnslot) > + { > + *epqreturnslot = epqslot; > + return NULL; > + } > > comment update missed? Well, you'd deleted a comment around there ;). I've added something back now... Greetings, Andres Freund --qw3ipai4e5xhdytn Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v20-0001-Expand-EPQ-tests-for-UPDATEs-and-DELETEs.patch" From 3e5458614abf572bd6afc83ce203b4fec8f18363 Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Wed, 20 Mar 2019 11:44:01 -0700 Subject: [PATCH v20 1/2] Expand EPQ tests for UPDATEs and DELETEs Previously there was basically no coverage for UPDATEs encountering deleted rows, and no coverage for DELETE having to perform EPQ. That's problematic for an upcoming commit in which EPQ is tought to integrate with tableams. Author: Andres Freund --- .../isolation/expected/eval-plan-qual.out | 218 +++++++++++++++++- src/test/isolation/specs/eval-plan-qual.spec | 33 ++- 2 files changed, 241 insertions(+), 10 deletions(-) diff --git a/src/test/isolation/expected/eval-plan-qual.out b/src/test/isolation/expected/eval-plan-qual.out index bbbb62ef4b1..bab01e0788a 100644 --- a/src/test/isolation/expected/eval-plan-qual.out +++ b/src/test/isolation/expected/eval-plan-qual.out @@ -1,10 +1,16 @@ Parsed test spec with 3 sessions starting permutation: wx1 wx2 c1 c2 read -step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; -step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; +balance + +400 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; step c1: COMMIT; step wx2: <... completed> +balance + +850 step c2: COMMIT; step read: SELECT * FROM accounts ORDER BY accountid; accountid balance @@ -13,10 +19,15 @@ checking 850 savings 600 starting permutation: wy1 wy2 c1 c2 read -step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking'; -step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000; +step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance; +balance + +1100 +step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; step c1: COMMIT; step wy2: <... completed> +balance + step c2: COMMIT; step read: SELECT * FROM accounts ORDER BY accountid; accountid balance @@ -24,6 +35,195 @@ accountid balance checking 1100 savings 600 +starting permutation: wx1 wx2 r1 c2 read +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; +balance + +400 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +step r1: ROLLBACK; +step wx2: <... completed> +balance + +1050 +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +checking 1050 +savings 600 + +starting permutation: wy1 wy2 r1 c2 read +step wy1: UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance; +balance + +1100 +step wy2: UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; +step r1: ROLLBACK; +step wy2: <... completed> +balance + +1600 +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +checking 1600 +savings 600 + +starting permutation: wx1 d1 wx2 c1 c2 read +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; +balance + +400 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +balance + +400 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +step c1: COMMIT; +step wx2: <... completed> +balance + +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + +starting permutation: wx2 d1 c2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step c2: COMMIT; +step d1: <... completed> +balance + +1050 +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + +starting permutation: wx2 wx2 d1 c2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1500 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step c2: COMMIT; +step d1: <... completed> +balance + +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +checking 1500 +savings 600 + +starting permutation: wx2 d2 d1 c2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step d2: DELETE FROM accounts WHERE accountid = 'checking'; +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step c2: COMMIT; +step d1: <... completed> +balance + +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + +starting permutation: wx1 d1 wx2 r1 c2 read +step wx1: UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; +balance + +400 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +balance + +400 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +step r1: ROLLBACK; +step wx2: <... completed> +balance + +1050 +step c2: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +checking 1050 +savings 600 + +starting permutation: wx2 d1 r2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step r2: ROLLBACK; +step d1: <... completed> +balance + +600 +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + +starting permutation: wx2 wx2 d1 r2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1500 +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step r2: ROLLBACK; +step d1: <... completed> +balance + +600 +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + +starting permutation: wx2 d2 d1 r2 c1 read +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 +step d2: DELETE FROM accounts WHERE accountid = 'checking'; +step d1: DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; +step r2: ROLLBACK; +step d1: <... completed> +balance + +600 +step c1: COMMIT; +step read: SELECT * FROM accounts ORDER BY accountid; +accountid balance + +savings 600 + starting permutation: upsert1 upsert2 c1 c2 read step upsert1: WITH upsert AS @@ -106,7 +306,10 @@ a b c step c2: COMMIT; starting permutation: wx2 partiallock c2 c1 read -step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 step partiallock: SELECT * FROM accounts a1, accounts a2 WHERE a1.accountid = a2.accountid @@ -126,7 +329,10 @@ checking 1050 savings 600 starting permutation: wx2 lockwithvalues c2 c1 read -step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; +step wx2: UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; +balance + +1050 step lockwithvalues: SELECT * FROM accounts a1, (values('checking'),('savings')) v(id) WHERE a1.accountid = v.id diff --git a/src/test/isolation/specs/eval-plan-qual.spec b/src/test/isolation/specs/eval-plan-qual.spec index 2e1b5095e8e..c22bf65f42f 100644 --- a/src/test/isolation/specs/eval-plan-qual.spec +++ b/src/test/isolation/specs/eval-plan-qual.spec @@ -42,9 +42,16 @@ teardown session "s1" setup { BEGIN ISOLATION LEVEL READ COMMITTED; } # wx1 then wx2 checks the basic case of re-fetching up-to-date values -step "wx1" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking'; } +step "wx1" { UPDATE accounts SET balance = balance - 200 WHERE accountid = 'checking' RETURNING balance; } # wy1 then wy2 checks the case where quals pass then fail -step "wy1" { UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking'; } +step "wy1" { UPDATE accounts SET balance = balance + 500 WHERE accountid = 'checking' RETURNING balance; } + +# d1 then wx1 checks that update can deal with the updated row vanishing +# wx2 then d1 checks that the delete affects the updated row +# wx2, wx2 then d1 checks that the delete checks the quals correctly (balance too high) +# wx2, d2, then d1 checks that delete handles a vanishing row correctly +step "d1" { DELETE FROM accounts WHERE accountid = 'checking' AND balance < 1500 RETURNING balance; } + # upsert tests are to check writable-CTE cases step "upsert1" { WITH upsert AS @@ -64,6 +71,7 @@ step "readp1" { SELECT tableoid::regclass, ctid, * FROM p WHERE b IN (0, 1) AND step "writep1" { UPDATE p SET b = -1 WHERE a = 1 AND b = 1 AND c = 0; } step "writep2" { UPDATE p SET b = -b WHERE a = 1 AND c = 0; } step "c1" { COMMIT; } +step "r1" { ROLLBACK; } # these tests are meant to exercise EvalPlanQualFetchRowMarks, # ie, handling non-locked tables in an EvalPlanQual recheck @@ -128,8 +136,10 @@ step "selectresultforupdate" { session "s2" setup { BEGIN ISOLATION LEVEL READ COMMITTED; } -step "wx2" { UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking'; } -step "wy2" { UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000; } +step "wx2" { UPDATE accounts SET balance = balance + 450 WHERE accountid = 'checking' RETURNING balance; } +step "wy2" { UPDATE accounts SET balance = balance + 1000 WHERE accountid = 'checking' AND balance < 1000 RETURNING balance; } +step "d2" { DELETE FROM accounts WHERE accountid = 'checking'; } + step "upsert2" { WITH upsert AS (UPDATE accounts SET balance = balance + 1234 @@ -161,6 +171,7 @@ step "updateforcip3" { step "wrtwcte" { UPDATE table_a SET value = 'tableAValue2' WHERE id = 1; } step "wrjt" { UPDATE jointest SET data = 42 WHERE id = 7; } step "c2" { COMMIT; } +step "r2" { ROLLBACK; } session "s3" setup { BEGIN ISOLATION LEVEL READ COMMITTED; } @@ -192,8 +203,22 @@ step "multireadwcte" { teardown { COMMIT; } +# test that normal update follows update chains, and reverifies quals permutation "wx1" "wx2" "c1" "c2" "read" permutation "wy1" "wy2" "c1" "c2" "read" +permutation "wx1" "wx2" "r1" "c2" "read" +permutation "wy1" "wy2" "r1" "c2" "read" + +# test that deletes follow chains, and reverifies quals +permutation "wx1" "d1" "wx2" "c1" "c2" "read" +permutation "wx2" "d1" "c2" "c1" "read" +permutation "wx2" "wx2" "d1" "c2" "c1" "read" +permutation "wx2" "d2" "d1" "c2" "c1" "read" +permutation "wx1" "d1" "wx2" "r1" "c2" "read" +permutation "wx2" "d1" "r2" "c1" "read" +permutation "wx2" "wx2" "d1" "r2" "c1" "read" +permutation "wx2" "d2" "d1" "r2" "c1" "read" + permutation "upsert1" "upsert2" "c1" "c2" "read" permutation "readp1" "writep1" "readp2" "c1" "c2" permutation "writep2" "returningp1" "c1" "c2" -- 2.21.0.dirty --qw3ipai4e5xhdytn Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v20-0002-tableam-Add-insert-delete-update-lock_tuple.patch" From f72cc54402dbf051c52657e843c9525c1aea972c Mon Sep 17 00:00:00 2001 From: Andres Freund Date: Thu, 7 Mar 2019 16:23:34 -0800 Subject: [PATCH v20 2/2] tableam: Add insert, delete, update, lock_tuple. Author: Reviewed-By: Discussion: https://postgr.es/m/ Backpatch: --- contrib/pgrowlocks/pgrowlocks.c | 6 +- src/backend/access/heap/heapam.c | 395 +++++++--------- src/backend/access/heap/heapam_handler.c | 324 +++++++++++++ src/backend/access/heap/heapam_visibility.c | 105 +++-- src/backend/access/heap/tuptoaster.c | 2 +- src/backend/access/table/tableam.c | 113 +++++ src/backend/access/table/tableamapi.c | 13 + src/backend/commands/copy.c | 3 +- src/backend/commands/trigger.c | 109 ++--- src/backend/executor/execIndexing.c | 4 +- src/backend/executor/execMain.c | 289 +----------- src/backend/executor/execReplication.c | 137 +++--- src/backend/executor/nodeLockRows.c | 137 ++---- src/backend/executor/nodeModifyTable.c | 424 ++++++++++-------- src/backend/executor/nodeTidscan.c | 2 +- src/include/access/heapam.h | 58 +-- src/include/access/tableam.h | 347 ++++++++++++++ src/include/executor/executor.h | 12 +- src/include/nodes/lockoptions.h | 5 + src/include/utils/snapshot.h | 13 - .../expected/partition-key-update-1.out | 2 +- src/tools/pgindent/typedefs.list | 4 +- 22 files changed, 1465 insertions(+), 1039 deletions(-) diff --git a/contrib/pgrowlocks/pgrowlocks.c b/contrib/pgrowlocks/pgrowlocks.c index 2d2a6cf1533..54514309091 100644 --- a/contrib/pgrowlocks/pgrowlocks.c +++ b/contrib/pgrowlocks/pgrowlocks.c @@ -146,7 +146,7 @@ pgrowlocks(PG_FUNCTION_ARGS) /* scan the relation */ while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { - HTSU_Result htsu; + TM_Result htsu; TransactionId xmax; uint16 infomask; @@ -160,9 +160,9 @@ pgrowlocks(PG_FUNCTION_ARGS) infomask = tuple->t_data->t_infomask; /* - * A tuple is locked if HTSU returns BeingUpdated. + * A tuple is locked if HTSU returns BeingModified. */ - if (htsu == HeapTupleBeingUpdated) + if (htsu == TableTupleBeingModified) { char **values; diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 3c8a5da0bc8..698faa4a83e 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -86,7 +86,7 @@ static void compute_new_xmax_infomask(TransactionId xmax, uint16 old_infomask, LockTupleMode mode, bool is_update, TransactionId *result_xmax, uint16 *result_infomask, uint16 *result_infomask2); -static HTSU_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, +static TM_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, TransactionId xid, LockTupleMode mode); static void GetMultiXactIdHintBits(MultiXactId multi, uint16 *new_infomask, @@ -1389,7 +1389,6 @@ heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tuple, Buffer *userbuf, - bool keep_buf, Relation stats_relation) { ItemPointer tid = &(tuple->t_self); @@ -1419,13 +1418,8 @@ heap_fetch(Relation relation, if (offnum < FirstOffsetNumber || offnum > PageGetMaxOffsetNumber(page)) { LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; tuple->t_data = NULL; return false; } @@ -1441,13 +1435,8 @@ heap_fetch(Relation relation, if (!ItemIdIsNormal(lp)) { LockBuffer(buffer, BUFFER_LOCK_UNLOCK); - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; tuple->t_data = NULL; return false; } @@ -1486,14 +1475,9 @@ heap_fetch(Relation relation, return true; } - /* Tuple failed time qual, but maybe caller wants to see it anyway. */ - if (keep_buf) - *userbuf = buffer; - else - { - ReleaseBuffer(buffer); - *userbuf = InvalidBuffer; - } + /* Tuple failed time qual */ + ReleaseBuffer(buffer); + *userbuf = InvalidBuffer; return false; } @@ -1886,40 +1870,12 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate) * The new tuple is stamped with current transaction ID and the specified * command ID. * - * If the HEAP_INSERT_SKIP_WAL option is specified, the new tuple is not - * logged in WAL, even for a non-temp relation. Safe usage of this behavior - * requires that we arrange that all new tuples go into new pages not - * containing any tuples from other transactions, and that the relation gets - * fsync'd before commit. (See also heap_sync() comments) + * See table_insert for comments about most of the input flags, except that + * this routine directly takes a tuple rather than a slot. * - * The HEAP_INSERT_SKIP_FSM option is passed directly to - * RelationGetBufferForTuple, which see for more info. - * - * HEAP_INSERT_FROZEN should only be specified for inserts into - * relfilenodes created during the current subtransaction and when - * there are no prior snapshots or pre-existing portals open. - * This causes rows to be frozen, which is an MVCC violation and - * requires explicit options chosen by user. - * - * HEAP_INSERT_SPECULATIVE is used on so-called "speculative insertions", - * which can be backed out afterwards without aborting the whole transaction. - * Other sessions can wait for the speculative insertion to be confirmed, - * turning it into a regular tuple, or aborted, as if it never existed. - * Speculatively inserted tuples behave as "value locks" of short duration, - * used to implement INSERT .. ON CONFLICT. - * - * HEAP_INSERT_NO_LOGICAL force-disables the emitting of logical decoding - * information for the tuple. This should solely be used during table rewrites - * where RelationIsLogicallyLogged(relation) is not yet accurate for the new - * relation. - * - * Note that most of these options will be applied when inserting into the - * heap's TOAST table, too, if the tuple requires any out-of-line data. Only - * HEAP_INSERT_SPECULATIVE is explicitly ignored, as the toast data does not - * partake in speculative insertion. - * - * The BulkInsertState object (if any; bistate can be NULL for default - * behavior) is also just passed through to RelationGetBufferForTuple. + * There's corresponding HEAP_INSERT_ options to all the TABLE_INSERT_ + * options, and there additionally is HEAP_INSERT_SPECULATIVE which is used to + * implement table_insert_speculative(). * * On return the header fields of *tup are updated to match the stored tuple; * in particular tup->t_self receives the actual TID where the tuple was @@ -2489,36 +2445,20 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) /* * heap_delete - delete a tuple * - * NB: do not call this directly unless you are prepared to deal with - * concurrent-update conditions. Use simple_heap_delete instead. + * See table_delete() for an explanation of the parameters. * - * relation - table to be modified (caller must hold suitable lock) - * tid - TID of tuple to be deleted - * cid - delete command ID (used for visibility test, and stored into - * cmax if successful) - * crosscheck - if not InvalidSnapshot, also check tuple against this - * wait - true if should wait for any conflicting update to commit/abort - * hufd - output parameter, filled in failure cases (see below) - * changingPart - true iff the tuple is being moved to another partition - * table due to an update of the partition key. Otherwise, false. * - * Normal, successful return value is HeapTupleMayBeUpdated, which - * actually means we did delete it. Failure return codes are - * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated - * (the last only possible if wait == false). - * - * In the failure cases, the routine fills *hufd with the tuple's t_ctid, + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax - * (the last only for HeapTupleSelfUpdated, since we + * (the last only for TableTupleSelfModified, since we * cannot obtain cmax from a combocid generated by another transaction). - * See comments for struct HeapUpdateFailureData for additional info. */ -HTSU_Result +TM_Result heap_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, bool changingPart) + TM_FailureData *tmfd, bool changingPart) { - HTSU_Result result; + TM_Result result; TransactionId xid = GetCurrentTransactionId(); ItemId lp; HeapTupleData tp; @@ -2586,14 +2526,14 @@ heap_delete(Relation relation, ItemPointer tid, l1: result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("attempted to delete invisible tuple"))); } - else if (result == HeapTupleBeingUpdated && wait) + else if (result == TableTupleBeingModified && wait) { TransactionId xwait; uint16 infomask; @@ -2687,35 +2627,38 @@ l1: if ((tp.t_data->t_infomask & HEAP_XMAX_INVALID) || HEAP_XMAX_IS_LOCKED_ONLY(tp.t_data->t_infomask) || HeapTupleHeaderIsOnlyLocked(tp.t_data)) - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; else - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if (crosscheck != InvalidSnapshot && result == TableTupleMayBeModified) { /* Perform additional check for transaction-snapshot mode RI updates */ if (!HeapTupleSatisfiesVisibility(&tp, crosscheck, buffer)) - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || - result == HeapTupleUpdated || - result == HeapTupleBeingUpdated); + Assert(result == TableTupleSelfModified || + result == TableTupleUpdated || + result == TableTupleDeleted || + result == TableTupleBeingModified); Assert(!(tp.t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = tp.t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(tp.t_data); + tmfd->ctid = tp.t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(tp.t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(tp.t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; UnlockReleaseBuffer(buffer); if (have_tuple_lock) UnlockTupleTuplock(relation, &(tp.t_self), LockTupleExclusive); if (vmbuffer != InvalidBuffer) ReleaseBuffer(vmbuffer); + if (result == TableTupleUpdated && ItemPointerEquals(tid, &tmfd->ctid)) + result = TableTupleDeleted; return result; } @@ -2896,7 +2839,7 @@ l1: if (old_key_tuple != NULL && old_key_copied) heap_freetuple(old_key_tuple); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -2910,28 +2853,32 @@ l1: void simple_heap_delete(Relation relation, ItemPointer tid) { - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; result = heap_delete(relation, tid, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &hufd, false /* changingPart */ ); + &tmfd, false /* changingPart */ ); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* done successfully */ break; - case HeapTupleUpdated: + case TableTupleUpdated: elog(ERROR, "tuple concurrently updated"); break; + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_delete status: %u", result); break; @@ -2941,42 +2888,19 @@ simple_heap_delete(Relation relation, ItemPointer tid) /* * heap_update - replace a tuple * - * NB: do not call this directly unless you are prepared to deal with - * concurrent-update conditions. Use simple_heap_update instead. + * See table_update() for an explanation of the parameters. * - * relation - table to be modified (caller must hold suitable lock) - * otid - TID of old tuple to be replaced - * newtup - newly constructed tuple data to store - * cid - update command ID (used for visibility test, and stored into - * cmax/cmin if successful) - * crosscheck - if not InvalidSnapshot, also check old tuple against this - * wait - true if should wait for any conflicting update to commit/abort - * hufd - output parameter, filled in failure cases (see below) - * lockmode - output parameter, filled with lock mode acquired on tuple - * - * Normal, successful return value is HeapTupleMayBeUpdated, which - * actually means we *did* update it. Failure return codes are - * HeapTupleSelfUpdated, HeapTupleUpdated, or HeapTupleBeingUpdated - * (the last only possible if wait == false). - * - * On success, the header fields of *newtup are updated to match the new - * stored tuple; in particular, newtup->t_self is set to the TID where the - * new tuple was inserted, and its HEAP_ONLY_TUPLE flag is set iff a HOT - * update was done. However, any TOAST changes in the new tuple's - * data are not reflected into *newtup. - * - * In the failure cases, the routine fills *hufd with the tuple's t_ctid, - * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax - * (the last only for HeapTupleSelfUpdated, since we - * cannot obtain cmax from a combocid generated by another transaction). - * See comments for struct HeapUpdateFailureData for additional info. + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax (resolving a possible MultiXact, if necessary), and t_cmax (the last + * only for TableTupleSelfModified, since we cannot obtain cmax from a + * combocid generated by another transaction). */ -HTSU_Result +TM_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode) + TM_FailureData *tmfd, LockTupleMode *lockmode) { - HTSU_Result result; + TM_Result result; TransactionId xid = GetCurrentTransactionId(); Bitmapset *hot_attrs; Bitmapset *key_attrs; @@ -3150,16 +3074,16 @@ l2: result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer); /* see below about the "no wait" case */ - Assert(result != HeapTupleBeingUpdated || wait); + Assert(result != TableTupleBeingModified || wait); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { UnlockReleaseBuffer(buffer); ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("attempted to update invisible tuple"))); } - else if (result == HeapTupleBeingUpdated && wait) + else if (result == TableTupleBeingModified && wait) { TransactionId xwait; uint16 infomask; @@ -3250,7 +3174,7 @@ l2: * MultiXact. In that case, we need to check whether it committed * or aborted. If it aborted we are safe to update it again; * otherwise there is an update conflict, and we have to return - * HeapTupleUpdated below. + * TableTupleUpdated below. * * In the LockTupleExclusive case, we still need to preserve the * surviving members: those would include the tuple locks we had @@ -3322,28 +3246,29 @@ l2: can_continue = true; } - result = can_continue ? HeapTupleMayBeUpdated : HeapTupleUpdated; + result = can_continue ? TableTupleMayBeModified : TableTupleUpdated; } - if (crosscheck != InvalidSnapshot && result == HeapTupleMayBeUpdated) + if (crosscheck != InvalidSnapshot && result == TableTupleMayBeModified) { /* Perform additional check for transaction-snapshot mode RI updates */ if (!HeapTupleSatisfiesVisibility(&oldtup, crosscheck, buffer)) - result = HeapTupleUpdated; + result = TableTupleUpdated; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || - result == HeapTupleUpdated || - result == HeapTupleBeingUpdated); + Assert(result == TableTupleSelfModified || + result == TableTupleUpdated || + result == TableTupleDeleted || + result == TableTupleBeingModified); Assert(!(oldtup.t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = oldtup.t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data); + tmfd->ctid = oldtup.t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(oldtup.t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(oldtup.t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; UnlockReleaseBuffer(buffer); if (have_tuple_lock) UnlockTupleTuplock(relation, &(oldtup.t_self), *lockmode); @@ -3354,6 +3279,9 @@ l2: bms_free(id_attrs); bms_free(modified_attrs); bms_free(interesting_attrs); + // FIXME, this needs to be implemented above + if (result == TableTupleUpdated && ItemPointerEquals(otid, &tmfd->ctid)) + result = TableTupleDeleted; return result; } @@ -3828,7 +3756,7 @@ l2: bms_free(modified_attrs); bms_free(interesting_attrs); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -3948,29 +3876,33 @@ HeapDetermineModifiedColumns(Relation relation, Bitmapset *interesting_cols, void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup) { - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; LockTupleMode lockmode; result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &hufd, &lockmode); + &tmfd, &lockmode); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* Tuple was already updated in current command? */ elog(ERROR, "tuple already updated by self"); break; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* done successfully */ break; - case HeapTupleUpdated: + case TableTupleUpdated: elog(ERROR, "tuple concurrently updated"); break; + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + default: elog(ERROR, "unrecognized heap_update status: %u", result); break; @@ -4005,7 +3937,7 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * * Input parameters: * relation: relation containing tuple (caller must hold suitable lock) - * tuple->t_self: TID of tuple to lock (rest of struct need not be valid) + * tid: TID of tuple to lock * cid: current command ID (used for visibility test, and stored into * tuple's cmax if lock is successful) * mode: indicates if shared or exclusive tuple lock is desired @@ -4016,32 +3948,26 @@ get_mxact_status_for_lock(LockTupleMode mode, bool is_update) * Output parameters: * *tuple: all fields filled in * *buffer: set to buffer holding tuple (pinned but not locked at exit) - * *hufd: filled in failure cases (see below) + * *tmfd: filled in failure cases (see below) * - * Function result may be: - * HeapTupleMayBeUpdated: lock was successfully acquired - * HeapTupleInvisible: lock failed because tuple was never visible to us - * HeapTupleSelfUpdated: lock failed because tuple updated by self - * HeapTupleUpdated: lock failed because tuple updated by other xact - * HeapTupleWouldBlock: lock couldn't be acquired and wait_policy is skip + * Function results are the same as table_lock_tuple(). * - * In the failure cases other than HeapTupleInvisible, the routine fills - * *hufd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact, - * if necessary), and t_cmax (the last only for HeapTupleSelfUpdated, + * In the failure cases other than TableTupleInvisible, the routine fills + * *tmfd with the tuple's t_ctid, t_xmax (resolving a possible MultiXact, + * if necessary), and t_cmax (the last only for TableTupleSelfModified, * since we cannot obtain cmax from a combocid generated by another * transaction). - * See comments for struct HeapUpdateFailureData for additional info. + * See comments for struct TM_FailureData for additional info. * * See README.tuplock for a thorough explanation of this mechanism. */ -HTSU_Result -heap_lock_tuple(Relation relation, HeapTuple tuple, +TM_Result +heap_lock_tuple(Relation relation, ItemPointer tid, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_updates, - Buffer *buffer, HeapUpdateFailureData *hufd) + HeapTuple tuple, Buffer *buffer, TM_FailureData *tmfd) { - HTSU_Result result; - ItemPointer tid = &(tuple->t_self); + TM_Result result; ItemId lp; Page page; Buffer vmbuffer = InvalidBuffer; @@ -4076,11 +4002,12 @@ heap_lock_tuple(Relation relation, HeapTuple tuple, tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); tuple->t_len = ItemIdGetLength(lp); tuple->t_tableOid = RelationGetRelid(relation); + tuple->t_self = *tid; l3: result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer); - if (result == HeapTupleInvisible) + if (result == TableTupleInvisible) { /* * This is possible, but only when locking a tuple for ON CONFLICT @@ -4088,10 +4015,10 @@ l3: * order to give that case the opportunity to throw a more specific * error. */ - result = HeapTupleInvisible; + result = TableTupleInvisible; goto out_locked; } - else if (result == HeapTupleBeingUpdated || result == HeapTupleUpdated) + else if (result == TableTupleBeingModified || result == TableTupleUpdated || result == TableTupleDeleted) { TransactionId xwait; uint16 infomask; @@ -4147,7 +4074,7 @@ l3: if (TUPLOCK_from_mxstatus(members[i].status) >= mode) { pfree(members); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } } @@ -4163,20 +4090,20 @@ l3: Assert(HEAP_XMAX_IS_KEYSHR_LOCKED(infomask) || HEAP_XMAX_IS_SHR_LOCKED(infomask) || HEAP_XMAX_IS_EXCL_LOCKED(infomask)); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; case LockTupleShare: if (HEAP_XMAX_IS_SHR_LOCKED(infomask) || HEAP_XMAX_IS_EXCL_LOCKED(infomask)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; case LockTupleNoKeyExclusive: if (HEAP_XMAX_IS_EXCL_LOCKED(infomask)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; @@ -4184,7 +4111,7 @@ l3: if (HEAP_XMAX_IS_EXCL_LOCKED(infomask) && infomask2 & HEAP_KEYS_UPDATED) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } break; @@ -4233,12 +4160,12 @@ l3: */ if (follow_updates && updated) { - HTSU_Result res; + TM_Result res; res = heap_lock_updated_tuple(relation, tuple, &t_ctid, GetCurrentTransactionId(), mode); - if (res != HeapTupleMayBeUpdated) + if (res != TableTupleMayBeModified) { result = res; /* recovery code expects to have buffer lock held */ @@ -4371,7 +4298,7 @@ l3: * or we must wait for the locking transaction or multixact; so below * we ensure that we grab buffer lock after the sleep. */ - if (require_sleep && result == HeapTupleUpdated) + if (require_sleep && (result == TableTupleUpdated || result == TableTupleDeleted)) { LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4394,7 +4321,7 @@ l3: * This can only happen if wait_policy is Skip and the lock * couldn't be obtained. */ - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4420,7 +4347,7 @@ l3: status, infomask, relation, NULL)) { - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4460,7 +4387,7 @@ l3: case LockWaitSkip: if (!ConditionalXactLockTableWait(xwait)) { - result = HeapTupleWouldBlock; + result = TableTupleWouldBlock; /* recovery code expects to have buffer lock held */ LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE); goto failed; @@ -4479,12 +4406,12 @@ l3: /* if there are updates, follow the update chain */ if (follow_updates && !HEAP_XMAX_IS_LOCKED_ONLY(infomask)) { - HTSU_Result res; + TM_Result res; res = heap_lock_updated_tuple(relation, tuple, &t_ctid, GetCurrentTransactionId(), mode); - if (res != HeapTupleMayBeUpdated) + if (res != TableTupleMayBeModified) { result = res; /* recovery code expects to have buffer lock held */ @@ -4530,23 +4457,25 @@ l3: (tuple->t_data->t_infomask & HEAP_XMAX_INVALID) || HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_data->t_infomask) || HeapTupleHeaderIsOnlyLocked(tuple->t_data)) - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; + else if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid)) + result = TableTupleDeleted; else - result = HeapTupleUpdated; + result = TableTupleUpdated; } failed: - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { - Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated || - result == HeapTupleWouldBlock); + Assert(result == TableTupleSelfModified || result == TableTupleUpdated || + result == TableTupleWouldBlock || result == TableTupleDeleted); Assert(!(tuple->t_data->t_infomask & HEAP_XMAX_INVALID)); - hufd->ctid = tuple->t_data->t_ctid; - hufd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); - if (result == HeapTupleSelfUpdated) - hufd->cmax = HeapTupleHeaderGetCmax(tuple->t_data); + tmfd->ctid = tuple->t_data->t_ctid; + tmfd->xmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); + if (result == TableTupleSelfModified) + tmfd->cmax = HeapTupleHeaderGetCmax(tuple->t_data); else - hufd->cmax = InvalidCommandId; + tmfd->cmax = InvalidCommandId; goto out_locked; } @@ -4664,7 +4593,7 @@ failed: END_CRIT_SECTION(); - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; out_locked: LockBuffer(*buffer, BUFFER_LOCK_UNLOCK); @@ -5022,8 +4951,8 @@ l5: * with the given xid, does the current transaction need to wait, fail, or can * it continue if it wanted to acquire a lock of the given mode? "needwait" * is set to true if waiting is necessary; if it can continue, then - * HeapTupleMayBeUpdated is returned. If the lock is already held by the - * current transaction, return HeapTupleSelfUpdated. In case of a conflict + * TableTupleMayBeModified is returned. If the lock is already held by the + * current transaction, return TableTupleSelfModified. In case of a conflict * with another transaction, a different HeapTupleSatisfiesUpdate return code * is returned. * @@ -5031,7 +4960,7 @@ l5: * lock held by a single Xid, i.e. not a real MultiXactId; we express it this * way for simplicity of API. */ -static HTSU_Result +static TM_Result test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, LockTupleMode mode, bool *needwait) { @@ -5052,7 +4981,7 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * very rare but can happen if multiple transactions are trying to * lock an ancient version of the same tuple. */ - return HeapTupleSelfUpdated; + return TableTupleSelfModified; } else if (TransactionIdIsInProgress(xid)) { @@ -5072,10 +5001,10 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * If we set needwait above, then this value doesn't matter; * otherwise, this value signals to caller that it's okay to proceed. */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } else if (TransactionIdDidAbort(xid)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; else if (TransactionIdDidCommit(xid)) { /* @@ -5094,18 +5023,18 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * always be checked. */ if (!ISUPDATE_from_mxstatus(status)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (DoLockModesConflict(LOCKMODE_from_mxstatus(status), LOCKMODE_from_mxstatus(wantedstatus))) /* bummer */ - return HeapTupleUpdated; + return TableTupleUpdated; - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* Not in progress, not aborted, not committed -- must have crashed */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } @@ -5116,11 +5045,11 @@ test_lockmode_for_conflict(MultiXactStatus status, TransactionId xid, * xid with the given mode; if this tuple is updated, recurse to lock the new * version as well. */ -static HTSU_Result +static TM_Result heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, LockTupleMode mode) { - HTSU_Result result; + TM_Result result; ItemPointerData tupid; HeapTupleData mytup; Buffer buf; @@ -5145,7 +5074,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, block = ItemPointerGetBlockNumber(&tupid); ItemPointerCopy(&tupid, &(mytup.t_self)); - if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &mytup, &buf, NULL)) { /* * if we fail to find the updated version of the tuple, it's @@ -5154,7 +5083,7 @@ heap_lock_updated_tuple_rec(Relation rel, ItemPointer tid, TransactionId xid, * chain, and there's no further tuple to lock: return success to * caller. */ - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_unlocked; } @@ -5203,7 +5132,7 @@ l4: !TransactionIdEquals(HeapTupleHeaderGetXmin(mytup.t_data), priorXmax)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5214,7 +5143,7 @@ l4: */ if (TransactionIdDidAbort(HeapTupleHeaderGetXmin(mytup.t_data))) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5269,7 +5198,7 @@ l4: * this tuple and continue locking the next version in the * update chain. */ - if (result == HeapTupleSelfUpdated) + if (result == TableTupleSelfModified) { pfree(members); goto next; @@ -5284,7 +5213,7 @@ l4: pfree(members); goto l4; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { pfree(members); goto out_locked; @@ -5345,7 +5274,7 @@ l4: * either. We just need to skip this tuple and continue * locking the next version in the update chain. */ - if (result == HeapTupleSelfUpdated) + if (result == TableTupleSelfModified) goto next; if (needwait) @@ -5355,7 +5284,7 @@ l4: XLTW_LockUpdated); goto l4; } - if (result != HeapTupleMayBeUpdated) + if (result != TableTupleMayBeModified) { goto out_locked; } @@ -5415,7 +5344,7 @@ next: ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid) || HeapTupleHeaderIsOnlyLocked(mytup.t_data)) { - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; goto out_locked; } @@ -5425,9 +5354,14 @@ next: UnlockReleaseBuffer(buf); } - result = HeapTupleMayBeUpdated; + result = TableTupleMayBeModified; out_locked: + + // FIXME + if (result == TableTupleUpdated && ItemPointerEquals(&mytup.t_self, &mytup.t_data->t_ctid)) + result = TableTupleDeleted; + UnlockReleaseBuffer(buf); out_unlocked: @@ -5459,7 +5393,7 @@ out_unlocked: * transaction cannot be using repeatable read or serializable isolation * levels, because that would lead to a serializability failure. */ -static HTSU_Result +static TM_Result heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, TransactionId xid, LockTupleMode mode) { @@ -5485,7 +5419,7 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, } /* nothing to lock */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -5505,7 +5439,7 @@ heap_lock_updated_tuple(Relation rel, HeapTuple tuple, ItemPointer ctid, * An explicit confirmation WAL record also makes logical decoding simpler. */ void -heap_finish_speculative(Relation relation, HeapTuple tuple) +heap_finish_speculative(Relation relation, ItemPointer tid) { Buffer buffer; Page page; @@ -5513,11 +5447,11 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) ItemId lp = NULL; HeapTupleHeader htup; - buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(&(tuple->t_self))); + buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); page = (Page) BufferGetPage(buffer); - offnum = ItemPointerGetOffsetNumber(&(tuple->t_self)); + offnum = ItemPointerGetOffsetNumber(tid); if (PageGetMaxOffsetNumber(page) >= offnum) lp = PageGetItemId(page, offnum); @@ -5533,7 +5467,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) /* NO EREPORT(ERROR) from here till changes are logged */ START_CRIT_SECTION(); - Assert(HeapTupleHeaderIsSpeculative(tuple->t_data)); + Assert(HeapTupleHeaderIsSpeculative(htup)); MarkBufferDirty(buffer); @@ -5541,7 +5475,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) * Replace the speculative insertion token with a real t_ctid, pointing to * itself like it does on regular tuples. */ - htup->t_ctid = tuple->t_self; + htup->t_ctid = *tid; /* XLOG stuff */ if (RelationNeedsWAL(relation)) @@ -5549,7 +5483,7 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) xl_heap_confirm xlrec; XLogRecPtr recptr; - xlrec.offnum = ItemPointerGetOffsetNumber(&tuple->t_self); + xlrec.offnum = ItemPointerGetOffsetNumber(tid); XLogBeginInsert(); @@ -5596,10 +5530,9 @@ heap_finish_speculative(Relation relation, HeapTuple tuple) * confirmation records. */ void -heap_abort_speculative(Relation relation, HeapTuple tuple) +heap_abort_speculative(Relation relation, ItemPointer tid) { TransactionId xid = GetCurrentTransactionId(); - ItemPointer tid = &(tuple->t_self); ItemId lp; HeapTupleData tp; Page page; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 6a26fcef94c..0ec2f69b20e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -21,7 +21,9 @@ #include "access/heapam.h" #include "access/tableam.h" +#include "access/xact.h" #include "storage/bufmgr.h" +#include "storage/lmgr.h" #include "utils/builtins.h" @@ -169,6 +171,321 @@ heapam_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, } +/* ---------------------------------------------------------------------------- + * Functions for manipulations of physical tuples for heap AM. + * ---------------------------------------------------------------------------- + */ + +static void +heapam_tuple_insert(Relation relation, TupleTableSlot *slot, CommandId cid, + int options, BulkInsertState bistate) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + /* Perform the insertion, and copy the resulting ItemPointer */ + heap_insert(relation, tuple, cid, options, bistate); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static void +heapam_tuple_insert_speculative(Relation relation, TupleTableSlot *slot, CommandId cid, + int options, BulkInsertState bistate, uint32 specToken) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken); + options |= HEAP_INSERT_SPECULATIVE; + + /* Perform the insertion, and copy the resulting ItemPointer */ + heap_insert(relation, tuple, cid, options, bistate); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static void +heapam_tuple_complete_speculative(Relation relation, TupleTableSlot *slot, uint32 spekToken, + bool succeeded) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + + /* adjust the tuple's state accordingly */ + if (!succeeded) + heap_finish_speculative(relation, &slot->tts_tid); + else + heap_abort_speculative(relation, &slot->tts_tid); + + if (shouldFree) + pfree(tuple); +} + +static TM_Result +heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) +{ + /* + * Currently Deleting of index tuples are handled at vacuum, in case if + * the storage itself is cleaning the dead tuples by itself, it is the + * time to call the index tuple deletion also. + */ + return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart); +} + + +static TM_Result +heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, + CommandId cid, Snapshot snapshot, Snapshot crosscheck, + bool wait, TM_FailureData *tmfd, + LockTupleMode *lockmode, bool *update_indexes) +{ + bool shouldFree = true; + HeapTuple tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); + TM_Result result; + + /* Update the tuple with table oid */ + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + + result = heap_update(relation, otid, tuple, cid, crosscheck, wait, + tmfd, lockmode); + ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + + /* + * Decide whether new index entries are needed for the tuple + * + * Note: heap_update returns the tid (location) of the new tuple in the + * t_self field. + * + * If it's a HOT update, we mustn't insert new index entries. + */ + *update_indexes = result == TableTupleMayBeModified && + !HeapTupleIsHeapOnly(tuple); + + if (shouldFree) + pfree(tuple); + + return result; +} + +static TM_Result +heapam_tuple_lock(Relation relation, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + TM_FailureData *tmfd) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + TM_Result result; + Buffer buffer; + HeapTuple tuple = &bslot->base.tupdata; + bool follow_updates; + + follow_updates = (flags & TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS) != 0; + tmfd->traversed = false; + + Assert(TTS_IS_BUFFERTUPLE(slot)); + +retry: + result = heap_lock_tuple(relation, tid, cid, mode, wait_policy, + follow_updates, tuple, &buffer, tmfd); + + if (result == TableTupleUpdated && + (flags & TUPLE_LOCK_FLAG_FIND_LAST_VERSION)) + { + ReleaseBuffer(buffer); + /* Should not encounter speculative tuple on recheck */ + Assert(!HeapTupleHeaderIsSpeculative(tuple->t_data)); + + if (!ItemPointerEquals(&tmfd->ctid, &tuple->t_self)) + { + SnapshotData SnapshotDirty; + TransactionId priorXmax; + + /* it was updated, so look at the updated version */ + *tid = tmfd->ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = tmfd->xmax; + + /* + * fetch target tuple + * + * Loop here to deal with updated or busy tuples + */ + InitDirtySnapshot(SnapshotDirty); + for (;;) + { + if (ItemPointerIndicatesMovedPartitions(tid)) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); + + tuple->t_self = *tid; + if (heap_fetch(relation, &SnapshotDirty, tuple, &buffer, NULL)) + { + /* + * If xmin isn't what we're expecting, the slot must have + * been recycled and reused for an unrelated tuple. This + * implies that the latest version of the row was deleted, + * so we need do nothing. (Should be safe to examine xmin + * without getting buffer's content lock. We assume + * reading a TransactionId to be atomic, and Xmin never + * changes in an existing tuple, except to invalid or + * frozen, and neither of those can match priorXmax.) + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data), + priorXmax)) + { + ReleaseBuffer(buffer); + return TableTupleDeleted; + } + + /* otherwise xmin should not be dirty... */ + if (TransactionIdIsValid(SnapshotDirty.xmin)) + elog(ERROR, "t_xmin is uncommitted in tuple to be updated"); + + /* + * If tuple is being updated by other transaction then we + * have to wait for its commit/abort, or die trying. + */ + if (TransactionIdIsValid(SnapshotDirty.xmax)) + { + ReleaseBuffer(buffer); + switch (wait_policy) + { + case LockWaitBlock: + XactLockTableWait(SnapshotDirty.xmax, + relation, &tuple->t_self, + XLTW_FetchUpdated); + break; + case LockWaitSkip: + if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) + /* skip instead of waiting */ + return TableTupleWouldBlock; + break; + case LockWaitError: + if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on row in relation \"%s\"", + RelationGetRelationName(relation)))); + break; + } + continue; /* loop back to repeat heap_fetch */ + } + + /* + * If tuple was inserted by our own transaction, we have + * to check cmin against es_output_cid: cmin >= current + * CID means our command cannot see the tuple, so we + * should ignore it. Otherwise heap_lock_tuple() will + * throw an error, and so would any later attempt to + * update or delete the tuple. (We need not check cmax + * because HeapTupleSatisfiesDirty will consider a tuple + * deleted by our transaction dead, regardless of cmax.) + * We just checked that priorXmax == xmin, so we can test + * that variable instead of doing HeapTupleHeaderGetXmin + * again. + */ + if (TransactionIdIsCurrentTransactionId(priorXmax) && + HeapTupleHeaderGetCmin(tuple->t_data) >= cid) + { + ReleaseBuffer(buffer); + return result; + } + + tmfd->traversed = true; + *tid = tuple->t_data->t_ctid; + ReleaseBuffer(buffer); + goto retry; + } + + /* + * If the referenced slot was actually empty, the latest + * version of the row must have been deleted, so we need do + * nothing. + */ + if (tuple->t_data == NULL) + { + return TableTupleDeleted; + } + + /* + * As above, if xmin isn't what we're expecting, do nothing. + */ + if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple->t_data), + priorXmax)) + { + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + return TableTupleDeleted; + } + + /* + * If we get here, the tuple was found but failed + * SnapshotDirty. Assuming the xmin is either a committed xact + * or our own xact (as it certainly should be if we're trying + * to modify the tuple), this must mean that the row was + * updated or deleted by either a committed xact or our own + * xact. If it was deleted, we can ignore it; if it was + * updated then chain up to the next version and repeat the + * whole process. + * + * As above, it should be safe to examine xmax and t_ctid + * without the buffer content lock, because they can't be + * changing. + */ + if (ItemPointerEquals(&tuple->t_self, &tuple->t_data->t_ctid)) + { + /* deleted, so forget about it */ + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + return TableTupleDeleted; + } + + /* updated, so look at the updated row */ + *tid = tuple->t_data->t_ctid; + /* updated row should have xmin matching this xmax */ + priorXmax = HeapTupleHeaderGetUpdateXid(tuple->t_data); + if (BufferIsValid(buffer)) + ReleaseBuffer(buffer); + /* loop back to fetch next in chain */ + } + } + else + { + /* tuple was deleted, so give up */ + return TableTupleDeleted; + } + } + + slot->tts_tableOid = RelationGetRelid(relation); + if (slot->tts_tableOid != InvalidOid) + tuple->t_tableOid = slot->tts_tableOid; + /* store in slot, transferring existing pin */ + ExecStorePinnedBufferHeapTuple(tuple, slot, buffer); + + return result; +} + + /* ------------------------------------------------------------------------ * Definition of the heap table access method. * ------------------------------------------------------------------------ @@ -193,6 +510,13 @@ static const TableAmRoutine heapam_methods = { .index_fetch_end = heapam_index_fetch_end, .index_fetch_tuple = heapam_index_fetch_tuple, + .tuple_insert = heapam_tuple_insert, + .tuple_insert_speculative = heapam_tuple_insert_speculative, + .tuple_complete_speculative = heapam_tuple_complete_speculative, + .tuple_delete = heapam_tuple_delete, + .tuple_update = heapam_tuple_update, + .tuple_lock = heapam_tuple_lock, + .tuple_satisfies_snapshot = heapam_tuple_satisfies_snapshot, }; diff --git a/src/backend/access/heap/heapam_visibility.c b/src/backend/access/heap/heapam_visibility.c index 6cb38f80c68..b9d0475cde1 100644 --- a/src/backend/access/heap/heapam_visibility.c +++ b/src/backend/access/heap/heapam_visibility.c @@ -67,6 +67,7 @@ #include "access/htup_details.h" #include "access/multixact.h" #include "access/subtrans.h" +#include "access/tableam.h" #include "access/transam.h" #include "access/xact.h" #include "access/xlog.h" @@ -433,24 +434,24 @@ HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, * * The possible return codes are: * - * HeapTupleInvisible: the tuple didn't exist at all when the scan started, + * TableTupleInvisible: the tuple didn't exist at all when the scan started, * e.g. it was created by a later CommandId. * - * HeapTupleMayBeUpdated: The tuple is valid and visible, so it may be + * TableTupleMayBeModified: The tuple is valid and visible, so it may be * updated. * - * HeapTupleSelfUpdated: The tuple was updated by the current transaction, + * TableTupleSelfModified: The tuple was updated by the current transaction, * after the current scan started. * - * HeapTupleUpdated: The tuple was updated by a committed transaction. + * TableTupleUpdated: The tuple was updated by a committed transaction. * - * HeapTupleBeingUpdated: The tuple is being updated by an in-progress + * TableTupleBeingModified: The tuple is being updated by an in-progress * transaction other than the current transaction. (Note: this includes * the case where the tuple is share-locked by a MultiXact, even if the * MultiXact includes the current transaction. Callers that want to * distinguish that case must test for it themselves.) */ -HTSU_Result +TM_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer) { @@ -462,7 +463,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (!HeapTupleHeaderXminCommitted(tuple)) { if (HeapTupleHeaderXminInvalid(tuple)) - return HeapTupleInvisible; + return TableTupleInvisible; /* Used by pre-9.0 binary upgrades */ if (tuple->t_infomask & HEAP_MOVED_OFF) @@ -470,14 +471,14 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, TransactionId xvac = HeapTupleHeaderGetXvac(tuple); if (TransactionIdIsCurrentTransactionId(xvac)) - return HeapTupleInvisible; + return TableTupleInvisible; if (!TransactionIdIsInProgress(xvac)) { if (TransactionIdDidCommit(xvac)) { SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, InvalidTransactionId); @@ -491,7 +492,7 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (!TransactionIdIsCurrentTransactionId(xvac)) { if (TransactionIdIsInProgress(xvac)) - return HeapTupleInvisible; + return TableTupleInvisible; if (TransactionIdDidCommit(xvac)) SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, InvalidTransactionId); @@ -499,17 +500,17 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } } } else if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tuple))) { if (HeapTupleHeaderGetCmin(tuple) >= curcid) - return HeapTupleInvisible; /* inserted after scan started */ + return TableTupleInvisible; /* inserted after scan started */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) { @@ -527,9 +528,9 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) { if (MultiXactIdIsRunning(xmax, true)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; else - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* @@ -538,8 +539,8 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, * locked/updated. */ if (!TransactionIdIsInProgress(xmax)) - return HeapTupleMayBeUpdated; - return HeapTupleBeingUpdated; + return TableTupleMayBeModified; + return TableTupleBeingModified; } if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) @@ -556,16 +557,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; - return HeapTupleMayBeUpdated; + return TableTupleBeingModified; + return TableTupleMayBeModified; } else { if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan + return TableTupleSelfModified; /* updated after scan * started */ else - return HeapTupleInvisible; /* updated before scan + return TableTupleInvisible; /* updated before scan * started */ } } @@ -575,16 +576,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, /* deleting subtransaction must have aborted */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } else if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmin(tuple))) - return HeapTupleInvisible; + return TableTupleInvisible; else if (TransactionIdDidCommit(HeapTupleHeaderGetRawXmin(tuple))) SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetRawXmin(tuple)); @@ -593,20 +594,23 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, /* it must have aborted or crashed */ SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); - return HeapTupleInvisible; + return TableTupleInvisible; } } /* by here, the inserting transaction has committed */ if (tuple->t_infomask & HEAP_XMAX_INVALID) /* xid invalid or aborted */ - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (tuple->t_infomask & HEAP_XMAX_COMMITTED) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleMayBeUpdated; - return HeapTupleUpdated; /* updated by other */ + return TableTupleMayBeModified; + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; /* deleted by other */ + else + return TableTupleUpdated; /* updated by other */ } if (tuple->t_infomask & HEAP_XMAX_IS_MULTI) @@ -614,22 +618,22 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, TransactionId xmax; if (HEAP_LOCKED_UPGRADED(tuple->t_infomask)) - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), true)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } xmax = HeapTupleGetUpdateXid(tuple); if (!TransactionIdIsValid(xmax)) { if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; } /* not LOCKED_ONLY, so it has to have an xmax */ @@ -638,16 +642,21 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, if (TransactionIdIsCurrentTransactionId(xmax)) { if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } if (MultiXactIdIsRunning(HeapTupleHeaderGetRawXmax(tuple), false)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (TransactionIdDidCommit(xmax)) - return HeapTupleUpdated; + { + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; + else + return TableTupleUpdated; + } /* * By here, the update in the Xmax is either aborted or crashed, but @@ -662,34 +671,34 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } else { /* There are lockers running */ - return HeapTupleBeingUpdated; + return TableTupleBeingModified; } } if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmax(tuple))) { if (HEAP_XMAX_IS_LOCKED_ONLY(tuple->t_infomask)) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (HeapTupleHeaderGetCmax(tuple) >= curcid) - return HeapTupleSelfUpdated; /* updated after scan started */ + return TableTupleSelfModified; /* updated after scan started */ else - return HeapTupleInvisible; /* updated before scan started */ + return TableTupleInvisible; /* updated before scan started */ } if (TransactionIdIsInProgress(HeapTupleHeaderGetRawXmax(tuple))) - return HeapTupleBeingUpdated; + return TableTupleBeingModified; if (!TransactionIdDidCommit(HeapTupleHeaderGetRawXmax(tuple))) { /* it must have aborted or crashed */ SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } /* xmax transaction committed */ @@ -698,12 +707,16 @@ HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, { SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); - return HeapTupleMayBeUpdated; + return TableTupleMayBeModified; } SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetRawXmax(tuple)); - return HeapTupleUpdated; /* updated by other */ + + if (ItemPointerEquals(&htup->t_self, &tuple->t_ctid)) + return TableTupleDeleted; /* deleted by other */ + else + return TableTupleUpdated; /* updated by other */ } /* diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index cd921a46005..a40cfcf1954 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -1763,7 +1763,7 @@ toast_delete_datum(Relation rel, Datum value, bool is_speculative) * Have a chunk, delete it */ if (is_speculative) - heap_abort_speculative(toastrel, toasttup); + heap_abort_speculative(toastrel, &toasttup->t_self); else simple_heap_delete(toastrel, &toasttup->t_self); } diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c index 628d930c130..540c1f99766 100644 --- a/src/backend/access/table/tableam.c +++ b/src/backend/access/table/tableam.c @@ -176,6 +176,119 @@ table_beginscan_parallel(Relation relation, ParallelTableScanDesc parallel_scan) } +/* ---------------------------------------------------------------------------- + * Functions to make modifications a bit simpler. + * ---------------------------------------------------------------------------- + */ + +/* + * simple_table_insert - insert a tuple + * + * Currently, this routine differs from table_insert only in supplying a + * default command ID and not allowing access to the speedup options. + */ +void +simple_table_insert(Relation rel, TupleTableSlot *slot) +{ + table_insert(rel, slot, GetCurrentCommandId(true), 0, NULL); +} + +/* + * simple_table_delete - delete a tuple + * + * This routine may be used to delete a tuple when concurrent updates of + * the target tuple are not expected (for example, because we have a lock + * on the relation associated with the tuple). Any failure is reported + * via ereport(). + */ +void +simple_table_delete(Relation rel, ItemPointer tid, Snapshot snapshot) +{ + TM_Result result; + TM_FailureData tmfd; + + result = table_delete(rel, tid, + GetCurrentCommandId(true), + snapshot, InvalidSnapshot, + true /* wait for commit */ , + &tmfd, false /* changingPart */ ); + + switch (result) + { + case TableTupleSelfModified: + /* Tuple was already updated in current command? */ + elog(ERROR, "tuple already updated by self"); + break; + + case TableTupleMayBeModified: + /* done successfully */ + break; + + case TableTupleUpdated: + elog(ERROR, "tuple concurrently updated"); + break; + + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + + default: + elog(ERROR, "unrecognized table_delete status: %u", result); + break; + } +} + +/* + * simple_table_update - replace a tuple + * + * This routine may be used to update a tuple when concurrent updates of + * the target tuple are not expected (for example, because we have a lock + * on the relation associated with the tuple). Any failure is reported + * via ereport(). + */ +void +simple_table_update(Relation rel, ItemPointer otid, + TupleTableSlot *slot, + Snapshot snapshot, + bool *update_indexes) +{ + TM_Result result; + TM_FailureData tmfd; + LockTupleMode lockmode; + + result = table_update(rel, otid, slot, + GetCurrentCommandId(true), + snapshot, InvalidSnapshot, + true /* wait for commit */ , + &tmfd, &lockmode, update_indexes); + + switch (result) + { + case TableTupleSelfModified: + /* Tuple was already updated in current command? */ + elog(ERROR, "tuple already updated by self"); + break; + + case TableTupleMayBeModified: + /* done successfully */ + break; + + case TableTupleUpdated: + elog(ERROR, "tuple concurrently updated"); + break; + + case TableTupleDeleted: + elog(ERROR, "tuple concurrently deleted"); + break; + + default: + elog(ERROR, "unrecognized table_update status: %u", result); + break; + } + +} + + /* ---------------------------------------------------------------------------- * Helper functions to implement parallel scans for block oriented AMs. * ---------------------------------------------------------------------------- diff --git a/src/backend/access/table/tableamapi.c b/src/backend/access/table/tableamapi.c index 3d3b82e1e58..c8592060112 100644 --- a/src/backend/access/table/tableamapi.c +++ b/src/backend/access/table/tableamapi.c @@ -64,6 +64,19 @@ GetTableAmRoutine(Oid amhandler) Assert(routine->tuple_satisfies_snapshot != NULL); + Assert(routine->tuple_insert != NULL); + + /* + * Could be made optional, but would require throwing error during + * parse-analysis. + */ + Assert(routine->tuple_insert_speculative != NULL); + Assert(routine->tuple_complete_speculative != NULL); + + Assert(routine->tuple_delete != NULL); + Assert(routine->tuple_update != NULL); + Assert(routine->tuple_lock != NULL); + return routine; } diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 218a6e01cbb..705df8900ba 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -3007,7 +3007,6 @@ CopyFrom(CopyState cstate) /* And create index entries for it */ if (resultRelInfo->ri_NumIndices > 0) recheckIndexes = ExecInsertIndexTuples(slot, - &(tuple->t_self), estate, false, NULL, @@ -3151,7 +3150,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, cstate->cur_lineno = firstBufferedLineNo + i; ExecStoreHeapTuple(bufferedTuples[i], myslot, false); recheckIndexes = - ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), + ExecInsertIndexTuples(myslot, estate, false, NULL, NIL); ExecARInsertTriggers(estate, resultRelInfo, myslot, diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 71098896947..2221188ea47 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -15,6 +15,7 @@ #include "access/genam.h" #include "access/heapam.h" +#include "access/tableam.h" #include "access/sysattr.h" #include "access/htup_details.h" #include "access/xact.h" @@ -3285,19 +3286,11 @@ GetTupleForTrigger(EState *estate, TupleTableSlot **newSlot) { Relation relation = relinfo->ri_RelationDesc; - HeapTuple tuple; - Buffer buffer; - BufferHeapTupleTableSlot *boldslot; - - Assert(TTS_IS_BUFFERTUPLE(oldslot)); - ExecClearTuple(oldslot); - boldslot = (BufferHeapTupleTableSlot *) oldslot; - tuple = &boldslot->base.tupdata; if (newSlot != NULL) { - HTSU_Result test; - HeapUpdateFailureData hufd; + TM_Result test; + TM_FailureData tmfd; *newSlot = NULL; @@ -3307,15 +3300,15 @@ GetTupleForTrigger(EState *estate, /* * lock tuple for update */ -ltrmark:; - tuple->t_self = *tid; - test = heap_lock_tuple(relation, tuple, - estate->es_output_cid, - lockmode, LockWaitBlock, - false, &buffer, &hufd); + test = table_lock_tuple(relation, tid, estate->es_snapshot, oldslot, + estate->es_output_cid, + lockmode, LockWaitBlock, + IsolationUsesXactSnapshot() ? 0 : TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + switch (test) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -3325,73 +3318,59 @@ ltrmark:; * enumerated in ExecUpdate and ExecDelete in * nodeModifyTable.c. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), errhint("Consider using an AFTER trigger instead of a BEFORE trigger to propagate changes to other rows."))); /* treat it as deleted; do not process */ - ReleaseBuffer(buffer); return false; - case HeapTupleMayBeUpdated: - ExecStorePinnedBufferHeapTuple(tuple, oldslot, buffer); - - break; - - case HeapTupleUpdated: - ReleaseBuffer(buffer); - if (IsolationUsesXactSnapshot()) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(&hufd.ctid, &tuple->t_self)) + case TableTupleMayBeModified: + if (tmfd.traversed) { - /* it was updated, so look at the updated version */ TupleTableSlot *epqslot; epqslot = EvalPlanQual(estate, epqstate, relation, relinfo->ri_RangeTableIndex, - lockmode, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(epqslot)) - { - *tid = hufd.ctid; + oldslot); - *newSlot = epqslot; + /* + * If PlanQual failed for updated tuple - we must not + * process this tuple! + */ + if (TupIsNull(epqslot)) + return false; - /* - * EvalPlanQual already locked the tuple, but we - * re-call heap_lock_tuple anyway as an easy way of - * re-fetching the correct tuple. Speed is hardly a - * criterion in this path anyhow. - */ - goto ltrmark; - } + *newSlot = epqslot; } + break; - /* - * if tuple was deleted or PlanQual failed for updated tuple - - * we must not process this tuple! - */ + case TableTupleUpdated: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + elog(ERROR, "unexpected table_lock_tuple status: %u", test); + break; + + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent delete"))); + /* tuple was deleted */ return false; - case HeapTupleInvisible: + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - ReleaseBuffer(buffer); - elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); return false; /* keep compiler quiet */ } } @@ -3399,6 +3378,14 @@ ltrmark:; { Page page; ItemId lp; + Buffer buffer; + BufferHeapTupleTableSlot *boldslot; + HeapTuple tuple; + + Assert(TTS_IS_BUFFERTUPLE(oldslot)); + ExecClearTuple(oldslot); + boldslot = (BufferHeapTupleTableSlot *) oldslot; + tuple = &boldslot->base.tupdata; buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid)); @@ -4286,7 +4273,7 @@ AfterTriggerExecute(EState *estate, LocTriggerData.tg_trigslot = ExecGetTriggerOldSlot(estate, relInfo); ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer, NULL)) elog(ERROR, "failed to fetch tuple1 for AFTER trigger"); ExecStorePinnedBufferHeapTuple(&tuple1, LocTriggerData.tg_trigslot, @@ -4310,7 +4297,7 @@ AfterTriggerExecute(EState *estate, LocTriggerData.tg_newslot = ExecGetTriggerNewSlot(estate, relInfo); ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer, NULL)) elog(ERROR, "failed to fetch tuple2 for AFTER trigger"); ExecStorePinnedBufferHeapTuple(&tuple2, LocTriggerData.tg_newslot, diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c index e67dd6750c6..3b602bb8baf 100644 --- a/src/backend/executor/execIndexing.c +++ b/src/backend/executor/execIndexing.c @@ -271,12 +271,12 @@ ExecCloseIndices(ResultRelInfo *resultRelInfo) */ List * ExecInsertIndexTuples(TupleTableSlot *slot, - ItemPointer tupleid, EState *estate, bool noDupErr, bool *specConflict, List *arbiterIndexes) { + ItemPointer tupleid = &slot->tts_tid; List *result = NIL; ResultRelInfo *resultRelInfo; int i; @@ -288,6 +288,8 @@ ExecInsertIndexTuples(TupleTableSlot *slot, Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; + Assert(ItemPointerIsValid(tupleid)); + /* * Get information from the result relation info structure. */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 63a34760eec..3a8e852b49d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2417,27 +2417,29 @@ ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist) /* - * Check a modified tuple to see if we want to process its updated version - * under READ COMMITTED rules. + * Check the updated version of a tuple to see if we want to process it under + * READ COMMITTED rules. * * estate - outer executor state data * epqstate - state for EvalPlanQual rechecking * relation - table containing tuple * rti - rangetable index of table containing tuple - * lockmode - requested tuple lock mode - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple + * inputslot - tuple for processing - this can be the slot from + * EvalPlanQualSlot(), for the increased efficiency. * - * *tid is also an output parameter: it's modified to hold the TID of the - * latest version of the tuple (note this may be changed even on failure) + * This tests whether the tuple in inputslot still matches the relvant + * quals. For that result to be useful, typically the input tuple has to be + * last row version (otherwise the result isn't particularly useful) and + * locked (otherwise the result might be out of date). That's typically + * achieved by using table_lock_tuple() with the + * TUPLE_LOCK_FLAG_FIND_LAST_VERSION flag.. * * Returns a slot containing the new candidate update/delete tuple, or * NULL if we determine we shouldn't process the row. */ TupleTableSlot * EvalPlanQual(EState *estate, EPQState *epqstate, - Relation relation, Index rti, LockTupleMode lockmode, - ItemPointer tid, TransactionId priorXmax) + Relation relation, Index rti, TupleTableSlot *inputslot) { TupleTableSlot *slot; TupleTableSlot *testslot; @@ -2450,19 +2452,12 @@ EvalPlanQual(EState *estate, EPQState *epqstate, EvalPlanQualBegin(epqstate, estate); /* - * Get and lock the updated version of the row; if fail, return NULL. + * Callers will often use the EvalPlanQualSlot to store the tuple to avoid + * an unnecessary copy. */ testslot = EvalPlanQualSlot(epqstate, relation, rti); - if (!EvalPlanQualFetch(estate, relation, lockmode, LockWaitBlock, - tid, priorXmax, - testslot)) - return NULL; - - /* - * For UPDATE/DELETE we have to return tid of actual row we're executing - * PQ for. - */ - *tid = testslot->tts_tid; + if (testslot != inputslot) + ExecCopySlot(testslot, inputslot); /* * Fetch any non-locked source rows @@ -2494,258 +2489,6 @@ EvalPlanQual(EState *estate, EPQState *epqstate, return slot; } -/* - * Fetch a copy of the newest version of an outdated tuple - * - * estate - executor state data - * relation - table containing tuple - * lockmode - requested tuple lock mode - * wait_policy - requested lock wait policy - * *tid - t_ctid from the outdated tuple (ie, next updated version) - * priorXmax - t_xmax from the outdated tuple - * slot - slot to store newest tuple version - * - * Returns true, with slot containing the newest tuple version, or false if we - * find that there is no newest version (ie, the row was deleted not updated). - * We also return false if the tuple is locked and the wait policy is to skip - * such tuples. - * - * If successful, we have locked the newest tuple version, so caller does not - * need to worry about it changing anymore. - */ -bool -EvalPlanQualFetch(EState *estate, Relation relation, LockTupleMode lockmode, - LockWaitPolicy wait_policy, - ItemPointer tid, TransactionId priorXmax, - TupleTableSlot *slot) -{ - HeapTupleData tuple; - SnapshotData SnapshotDirty; - - /* - * fetch target tuple - * - * Loop here to deal with updated or busy tuples - */ - InitDirtySnapshot(SnapshotDirty); - tuple.t_self = *tid; - for (;;) - { - Buffer buffer; - - if (heap_fetch(relation, &SnapshotDirty, &tuple, &buffer, true, NULL)) - { - HTSU_Result test; - HeapUpdateFailureData hufd; - - /* - * If xmin isn't what we're expecting, the slot must have been - * recycled and reused for an unrelated tuple. This implies that - * the latest version of the row was deleted, so we need do - * nothing. (Should be safe to examine xmin without getting - * buffer's content lock. We assume reading a TransactionId to be - * atomic, and Xmin never changes in an existing tuple, except to - * invalid or frozen, and neither of those can match priorXmax.) - */ - if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return false; - } - - /* otherwise xmin should not be dirty... */ - if (TransactionIdIsValid(SnapshotDirty.xmin)) - elog(ERROR, "t_xmin is uncommitted in tuple to be updated"); - - /* - * If tuple is being updated by other transaction then we have to - * wait for its commit/abort, or die trying. - */ - if (TransactionIdIsValid(SnapshotDirty.xmax)) - { - ReleaseBuffer(buffer); - switch (wait_policy) - { - case LockWaitBlock: - XactLockTableWait(SnapshotDirty.xmax, - relation, &tuple.t_self, - XLTW_FetchUpdated); - break; - case LockWaitSkip: - if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) - return false; /* skip instead of waiting */ - break; - case LockWaitError: - if (!ConditionalXactLockTableWait(SnapshotDirty.xmax)) - ereport(ERROR, - (errcode(ERRCODE_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on row in relation \"%s\"", - RelationGetRelationName(relation)))); - break; - } - continue; /* loop back to repeat heap_fetch */ - } - - /* - * If tuple was inserted by our own transaction, we have to check - * cmin against es_output_cid: cmin >= current CID means our - * command cannot see the tuple, so we should ignore it. Otherwise - * heap_lock_tuple() will throw an error, and so would any later - * attempt to update or delete the tuple. (We need not check cmax - * because HeapTupleSatisfiesDirty will consider a tuple deleted - * by our transaction dead, regardless of cmax.) We just checked - * that priorXmax == xmin, so we can test that variable instead of - * doing HeapTupleHeaderGetXmin again. - */ - if (TransactionIdIsCurrentTransactionId(priorXmax) && - HeapTupleHeaderGetCmin(tuple.t_data) >= estate->es_output_cid) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * This is a live tuple, so now try to lock it. - */ - test = heap_lock_tuple(relation, &tuple, - estate->es_output_cid, - lockmode, wait_policy, - false, &buffer, &hufd); - /* We now have two pins on the buffer, get rid of one */ - ReleaseBuffer(buffer); - - switch (test) - { - case HeapTupleSelfUpdated: - - /* - * The target tuple was already updated or deleted by the - * current command, or by a later command in the current - * transaction. We *must* ignore the tuple in the former - * case, so as to avoid the "Halloween problem" of - * repeated update attempts. In the latter case it might - * be sensible to fetch the updated tuple instead, but - * doing so would require changing heap_update and - * heap_delete to not complain about updating "invisible" - * tuples, which seems pretty scary (heap_lock_tuple will - * not complain, but few callers expect - * HeapTupleInvisible, and we're not one of them). So for - * now, treat the tuple as deleted and do not process. - */ - ReleaseBuffer(buffer); - return false; - - case HeapTupleMayBeUpdated: - /* successfully locked */ - break; - - case HeapTupleUpdated: - ReleaseBuffer(buffer); - if (IsolationUsesXactSnapshot()) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - /* Should not encounter speculative tuple on recheck */ - Assert(!HeapTupleHeaderIsSpeculative(tuple.t_data)); - if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self)) - { - /* it was updated, so look at the updated version */ - tuple.t_self = hufd.ctid; - /* updated row should have xmin matching this xmax */ - priorXmax = hufd.xmax; - continue; - } - /* tuple was deleted, so give up */ - return false; - - case HeapTupleWouldBlock: - ReleaseBuffer(buffer); - return false; - - case HeapTupleInvisible: - elog(ERROR, "attempted to lock invisible tuple"); - break; - - default: - ReleaseBuffer(buffer); - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); - return false; /* keep compiler quiet */ - } - - /* - * We got tuple - store it for use by the recheck query. - */ - ExecStorePinnedBufferHeapTuple(&tuple, slot, buffer); - ExecMaterializeSlot(slot); - break; - } - - /* - * If the referenced slot was actually empty, the latest version of - * the row must have been deleted, so we need do nothing. - */ - if (tuple.t_data == NULL) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * As above, if xmin isn't what we're expecting, do nothing. - */ - if (!TransactionIdEquals(HeapTupleHeaderGetXmin(tuple.t_data), - priorXmax)) - { - ReleaseBuffer(buffer); - return false; - } - - /* - * If we get here, the tuple was found but failed SnapshotDirty. - * Assuming the xmin is either a committed xact or our own xact (as it - * certainly should be if we're trying to modify the tuple), this must - * mean that the row was updated or deleted by either a committed xact - * or our own xact. If it was deleted, we can ignore it; if it was - * updated then chain up to the next version and repeat the whole - * process. - * - * As above, it should be safe to examine xmax and t_ctid without the - * buffer content lock, because they can't be changing. - */ - - /* check whether next version would be in a different partition */ - if (HeapTupleHeaderIndicatesMovedPartitions(tuple.t_data)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - /* check whether tuple has been deleted */ - if (ItemPointerEquals(&tuple.t_self, &tuple.t_data->t_ctid)) - { - /* deleted, so forget about it */ - ReleaseBuffer(buffer); - return false; - } - - /* updated, so look at the updated row */ - tuple.t_self = tuple.t_data->t_ctid; - /* updated row should have xmin matching this xmax */ - priorXmax = HeapTupleHeaderGetUpdateXid(tuple.t_data); - ReleaseBuffer(buffer); - /* loop back to fetch next in chain */ - } - - /* signal success */ - return true; -} - /* * EvalPlanQualInit -- initialize during creation of a plan state node * that might need to invoke EPQ processing. @@ -2911,7 +2654,7 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate) tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) + NULL)) elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); /* successful, store tuple */ diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 95dfc4987de..c8bdc224803 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -15,7 +15,6 @@ #include "postgres.h" #include "access/genam.h" -#include "access/heapam.h" #include "access/relscan.h" #include "access/tableam.h" #include "access/transam.h" @@ -166,35 +165,28 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; - HeapUpdateFailureData hufd; - HTSU_Result res; - HeapTupleData locktup; - HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - - /* Only a heap tuple has item pointers. */ - Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); - ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); + TM_FailureData tmfd; + TM_Result res; PushActiveSnapshot(GetLatestSnapshot()); - res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false), - lockmode, - LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - ReleaseBuffer(buf); + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + outslot, + GetCurrentCommandId(false), + lockmode, + LockWaitBlock, + 0 /* don't follow updates */ , + &tmfd); PopActiveSnapshot(); switch (res) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: /* XXX: Improve handling here */ - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid)) ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying"))); @@ -203,11 +195,17 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; - case HeapTupleInvisible: + case TableTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unexpected heap_lock_tuple status: %u", res); + elog(ERROR, "unexpected table_lock_tuple status: %u", res); break; } } @@ -330,35 +328,28 @@ retry: /* Found tuple, try to lock it in the lockmode. */ if (found) { - Buffer buf; - HeapUpdateFailureData hufd; - HTSU_Result res; - HeapTupleData locktup; - HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - - /* Only a heap tuple has item pointers. */ - Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); - ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); + TM_FailureData tmfd; + TM_Result res; PushActiveSnapshot(GetLatestSnapshot()); - res = heap_lock_tuple(rel, &locktup, GetCurrentCommandId(false), - lockmode, - LockWaitBlock, - false /* don't follow updates */ , - &buf, &hufd); - /* the tuple slot already has the buffer pinned */ - ReleaseBuffer(buf); + res = table_lock_tuple(rel, &(outslot->tts_tid), GetLatestSnapshot(), + outslot, + GetCurrentCommandId(false), + lockmode, + LockWaitBlock, + 0 /* don't follow updates */ , + &tmfd); PopActiveSnapshot(); switch (res) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: /* XXX: Improve handling here */ - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) + if (ItemPointerIndicatesMovedPartitions(&tmfd.ctid)) ereport(LOG, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be locked was already moved to another partition due to concurrent update, retrying"))); @@ -367,11 +358,17 @@ retry: (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("concurrent update, retrying"))); goto retry; - case HeapTupleInvisible: + case TableTupleDeleted: + /* XXX: Improve handling here */ + ereport(LOG, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("concurrent delete, retrying"))); + goto retry; + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unexpected heap_lock_tuple status: %u", res); + elog(ERROR, "unexpected table_lock_tuple status: %u", res); break; } } @@ -392,7 +389,6 @@ void ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) { bool skip_tuple = false; - HeapTuple tuple; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; @@ -419,16 +415,11 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) if (resultRelInfo->ri_PartitionCheck) ExecPartitionCheck(resultRelInfo, slot, estate, true); - /* Materialize slot into a tuple that we can scribble upon. */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* OK, store the tuple and create index entries for it */ - simple_heap_insert(rel, tuple); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + simple_table_insert(resultRelInfo->ri_RelationDesc, slot); if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); /* AFTER ROW INSERT Triggers */ @@ -456,13 +447,9 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, TupleTableSlot *searchslot, TupleTableSlot *slot) { bool skip_tuple = false; - HeapTuple tuple; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; - HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; - - /* We expect the searchslot to contain a heap tuple. */ - Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); + ItemPointer tid = &(searchslot->tts_tid); /* For now we support only tables. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); @@ -474,14 +461,14 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_update_before_row) { if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, - NULL, slot)) + tid, NULL, slot)) skip_tuple = true; /* "do nothing" */ } if (!skip_tuple) { List *recheckIndexes = NIL; + bool update_indexes; /* Check the constraints of the tuple */ if (rel->rd_att->constr) @@ -489,23 +476,16 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, if (resultRelInfo->ri_PartitionCheck) ExecPartitionCheck(resultRelInfo, slot, estate, true); - /* Materialize slot into a tuple that we can scribble upon. */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + simple_table_update(rel, tid, slot, + estate->es_snapshot, &update_indexes); - /* OK, update the tuple and index entries for it */ - simple_heap_update(rel, &hsearchslot->tuple->t_self, tuple); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); - - if (resultRelInfo->ri_NumIndices > 0 && - !HeapTupleIsHeapOnly(tuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), - estate, false, NULL, + if (resultRelInfo->ri_NumIndices > 0 && update_indexes) + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, - &(tuple->t_self), - NULL, slot, + tid, NULL, slot, recheckIndexes, NULL); list_free(recheckIndexes); @@ -525,11 +505,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, bool skip_tuple = false; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; - HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; - - /* For now we support only tables and heap tuples. */ - Assert(rel->rd_rel->relkind == RELKIND_RELATION); - Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); + ItemPointer tid = &searchslot->tts_tid; CheckCmdReplicaIdentity(rel, CMD_DELETE); @@ -538,23 +514,18 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, - NULL, NULL); + tid, NULL, NULL); } if (!skip_tuple) { - List *recheckIndexes = NIL; - /* OK, delete the tuple */ - simple_heap_delete(rel, &hsearchslot->tuple->t_self); + simple_table_delete(rel, tid, estate->es_snapshot); /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, - &hsearchslot->tuple->t_self, NULL, NULL); - - list_free(recheckIndexes); + tid, NULL, NULL); } } diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c index 76f0f9d66e5..cfa258fed96 100644 --- a/src/backend/executor/nodeLockRows.c +++ b/src/backend/executor/nodeLockRows.c @@ -23,6 +23,7 @@ #include "access/heapam.h" #include "access/htup_details.h" +#include "access/tableam.h" #include "access/xact.h" #include "executor/executor.h" #include "executor/nodeLockRows.h" @@ -82,11 +83,11 @@ lnext: ExecRowMark *erm = aerm->rowmark; Datum datum; bool isNull; - HeapTupleData tuple; - Buffer buffer; - HeapUpdateFailureData hufd; + ItemPointerData tid; + TM_FailureData tmfd; LockTupleMode lockmode; - HTSU_Result test; + int lockflags = 0; + TM_Result test; TupleTableSlot *markSlot; /* clear any leftover test tuple for this rel */ @@ -112,6 +113,7 @@ lnext: /* this child is inactive right now */ erm->ermActive = false; ItemPointerSetInvalid(&(erm->curCtid)); + ExecClearTuple(markSlot); continue; } } @@ -160,8 +162,8 @@ lnext: continue; } - /* okay, try to lock the tuple */ - tuple.t_self = *((ItemPointer) DatumGetPointer(datum)); + /* okay, try to lock (and fetch) the tuple */ + tid = *((ItemPointer) DatumGetPointer(datum)); switch (erm->markType) { case ROW_MARK_EXCLUSIVE: @@ -182,18 +184,23 @@ lnext: break; } - test = heap_lock_tuple(erm->relation, &tuple, - estate->es_output_cid, - lockmode, erm->waitPolicy, true, - &buffer, &hufd); - ReleaseBuffer(buffer); + lockflags = TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS; + if (!IsolationUsesXactSnapshot()) + lockflags |= TUPLE_LOCK_FLAG_FIND_LAST_VERSION; + + test = table_lock_tuple(erm->relation, &tid, estate->es_snapshot, + markSlot, estate->es_output_cid, + lockmode, erm->waitPolicy, + lockflags, + &tmfd); + switch (test) { - case HeapTupleWouldBlock: + case TableTupleWouldBlock: /* couldn't lock tuple in SKIP LOCKED mode */ goto lnext; - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -204,65 +211,48 @@ lnext: * to fetch the updated tuple instead, but doing so would * require changing heap_update and heap_delete to not * complain about updating "invisible" tuples, which seems - * pretty scary (heap_lock_tuple will not complain, but few - * callers expect HeapTupleInvisible, and we're not one of + * pretty scary (table_lock_tuple will not complain, but few + * callers expect TableTupleInvisible, and we're not one of * them). So for now, treat the tuple as deleted and do not * process. */ goto lnext; - case HeapTupleMayBeUpdated: - /* got the lock successfully */ + case TableTupleMayBeModified: + /* + * Got the lock successfully, the locked tuple saved in + * markSlot for, if needed, EvalPlanQual testing below. + */ + if (tmfd.traversed) + epq_needed = true; break; - case HeapTupleUpdated: + case TableTupleUpdated: if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - - if (ItemPointerEquals(&hufd.ctid, &tuple.t_self)) - { - /* Tuple was deleted, so don't return it */ - goto lnext; - } - - /* updated, so fetch and lock the updated version */ - if (!EvalPlanQualFetch(estate, erm->relation, - lockmode, erm->waitPolicy, - &hufd.ctid, hufd.xmax, - markSlot)) - { - /* - * Tuple was deleted; or it's locked and we're under SKIP - * LOCKED policy, so don't return it - */ - goto lnext; - } - /* remember the actually locked tuple's TID */ - tuple.t_self = markSlot->tts_tid; - - /* Remember we need to do EPQ testing */ - epq_needed = true; - - /* Continue loop until we have all target tuples */ + elog(ERROR, "unexpected table_lock_tuple status: %u", test); break; - case HeapTupleInvisible: + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + /* tuple was deleted so don't return it */ + goto lnext; + + case TableTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); break; default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", - test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); } /* Remember locked tuple's TID for EPQ testing and WHERE CURRENT OF */ - erm->curCtid = tuple.t_self; + erm->curCtid = tid; } /* @@ -270,49 +260,6 @@ lnext: */ if (epq_needed) { - /* - * Fetch a copy of any rows that were successfully locked without any - * update having occurred. (We do this in a separate pass so as to - * avoid overhead in the common case where there are no concurrent - * updates.) Make sure any inactive child rels have NULL test tuples - * in EPQ. - */ - foreach(lc, node->lr_arowMarks) - { - ExecAuxRowMark *aerm = (ExecAuxRowMark *) lfirst(lc); - ExecRowMark *erm = aerm->rowmark; - TupleTableSlot *markSlot; - HeapTupleData tuple; - Buffer buffer; - - markSlot = EvalPlanQualSlot(&node->lr_epqstate, erm->relation, erm->rti); - - /* skip non-active child tables, but clear their test tuples */ - if (!erm->ermActive) - { - Assert(erm->rti != erm->prti); /* check it's child table */ - ExecClearTuple(markSlot); - continue; - } - - /* was tuple updated and fetched above? */ - if (!TupIsNull(markSlot)) - continue; - - /* foreign tables should have been fetched above */ - Assert(erm->relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE); - Assert(ItemPointerIsValid(&(erm->curCtid))); - - /* okay, fetch the tuple */ - tuple.t_self = erm->curCtid; - if (!heap_fetch(erm->relation, SnapshotAny, &tuple, &buffer, - false, NULL)) - elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); - ExecStorePinnedBufferHeapTuple(&tuple, markSlot, buffer); - ExecMaterializeSlot(markSlot); - /* successful, use tuple in slot */ - } - /* * Now fetch any non-locked source rows --- the EPQ logic knows how to * do that. diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index fa92db130bb..c106d437a7b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -190,31 +190,33 @@ ExecProcessReturning(ResultRelInfo *resultRelInfo, */ static void ExecCheckHeapTupleVisible(EState *estate, - HeapTuple tuple, - Buffer buffer) + Relation rel, + TupleTableSlot *slot) { if (!IsolationUsesXactSnapshot()) return; - /* - * We need buffer pin and lock to call HeapTupleSatisfiesVisibility. - * Caller should be holding pin, but not lock. - */ - LockBuffer(buffer, BUFFER_LOCK_SHARE); - if (!HeapTupleSatisfiesVisibility(tuple, estate->es_snapshot, buffer)) + if (!table_tuple_satisfies_snapshot(rel, slot, estate->es_snapshot)) { + Datum xminDatum; + TransactionId xmin; + bool isnull; + + xminDatum = slot_getsysattr(slot, MinTransactionIdAttributeNumber, &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + /* * We should not raise a serialization failure if the conflict is * against a tuple inserted by our own transaction, even if it's not * visible to our snapshot. (This would happen, for example, if * conflicting keys are proposed for insertion in a single command.) */ - if (!TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) + if (!TransactionIdIsCurrentTransactionId(xmin)) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("could not serialize access due to concurrent update"))); } - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); } /* @@ -223,7 +225,8 @@ ExecCheckHeapTupleVisible(EState *estate, static void ExecCheckTIDVisible(EState *estate, ResultRelInfo *relinfo, - ItemPointer tid) + ItemPointer tid, + TupleTableSlot *tempSlot) { Relation rel = relinfo->ri_RelationDesc; Buffer buffer; @@ -234,10 +237,10 @@ ExecCheckTIDVisible(EState *estate, return; tuple.t_self = *tid; - if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple, &buffer, NULL)) elog(ERROR, "failed to fetch conflicting tuple for ON CONFLICT"); - ExecCheckHeapTupleVisible(estate, &tuple, buffer); - ReleaseBuffer(buffer); + ExecStorePinnedBufferHeapTuple(&tuple, tempSlot, buffer); + ExecCheckHeapTupleVisible(estate, rel, tempSlot); } /* ---------------------------------------------------------------- @@ -319,7 +322,6 @@ ExecInsert(ModifyTableState *mtstate, else { WCOKind wco_kind; - HeapTuple inserttuple; /* * Constraints might reference the tableoid column, so (re-)initialize @@ -417,16 +419,19 @@ ExecInsert(ModifyTableState *mtstate, * In case of ON CONFLICT DO NOTHING, do nothing. However, * verify that the tuple is visible to the executor's MVCC * snapshot at higher isolation levels. + * + * FIXME: Either comment or replace usage of + * ExecGetReturningSlot(). Need a slot that's compatible + * with the resultRelInfo table. */ Assert(onconflict == ONCONFLICT_NOTHING); - ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid); + ExecCheckTIDVisible(estate, resultRelInfo, &conflictTid, + ExecGetReturningSlot(estate, resultRelInfo)); InstrCountTuples2(&mtstate->ps, 1); return NULL; } } - inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * Before we start insertion proper, acquire our "speculative * insertion lock". Others can use that to wait for us to decide @@ -434,26 +439,22 @@ ExecInsert(ModifyTableState *mtstate, * waiting for the whole transaction to complete. */ specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId()); - HeapTupleHeaderSetSpeculativeToken(inserttuple->t_data, specToken); /* insert the tuple, with the speculative token */ - heap_insert(resultRelationDesc, inserttuple, - estate->es_output_cid, - HEAP_INSERT_SPECULATIVE, - NULL); - slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); + table_insert_speculative(resultRelationDesc, slot, + estate->es_output_cid, + 0, + NULL, + specToken); /* insert index entries for tuple */ - recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), + recheckIndexes = ExecInsertIndexTuples(slot, estate, true, &specConflict, arbiterIndexes); /* adjust the tuple's state accordingly */ - if (!specConflict) - heap_finish_speculative(resultRelationDesc, inserttuple); - else - heap_abort_speculative(resultRelationDesc, inserttuple); + table_complete_speculative(resultRelationDesc, slot, + specToken, specConflict); /* * Wake up anyone waiting for our decision. They will re-check @@ -479,23 +480,14 @@ ExecInsert(ModifyTableState *mtstate, } else { - /* - * insert the tuple normally. - * - * Note: heap_insert returns the tid (location) of the new tuple - * in the t_self field. - */ - inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); - heap_insert(resultRelationDesc, inserttuple, - estate->es_output_cid, - 0, NULL); - slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); + /* insert the tuple normally */ + table_insert(resultRelationDesc, slot, + estate->es_output_cid, + 0, NULL); /* insert index entries for tuple */ if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), - estate, false, NULL, + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); } } @@ -594,8 +586,8 @@ ExecDelete(ModifyTableState *mtstate, { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; TupleTableSlot *slot = NULL; TransitionCaptureState *ar_delete_trig_tcs; @@ -671,15 +663,17 @@ ExecDelete(ModifyTableState *mtstate, * mode transactions. */ ldelete:; - result = heap_delete(resultRelationDesc, tupleid, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &hufd, - changingPart); + result = table_delete(resultRelationDesc, tupleid, + estate->es_output_cid, + estate->es_snapshot, + estate->es_crosscheck_snapshot, + true /* wait for commit */ , + &tmfd, + changingPart); + switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -705,7 +699,7 @@ ldelete:; * can re-execute the DELETE and then return NULL to cancel * the outer delete. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), @@ -714,52 +708,97 @@ ldelete:; /* Else, already deleted by self; nothing to do */ return NULL; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: + { + TupleTableSlot *inputslot; + TupleTableSlot *epqslot; + + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + + /* + * Already know that we're going to need to do EPQ, so + * fetch tuple directly into the right slot. + */ + EvalPlanQualBegin(epqstate, estate); + inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc, + resultRelInfo->ri_RangeTableIndex); + + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + inputslot, estate->es_output_cid, + LockTupleExclusive, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + + switch (result) + { + case TableTupleMayBeModified: + Assert(tmfd.traversed); + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + inputslot); + if (TupIsNull(epqslot)) + /* Tuple not passing quals anymore, exiting... */ + return NULL; + + /* + * If requested, skip delete and pass back the updated + * row. + */ + if (epqreturnslot) + { + *epqreturnslot = epqslot; + return NULL; + } + else + goto ldelete; + + case TableTupleDeleted: + /* tuple already deleted; nothing to do */ + return NULL; + + default: + /* + * TableTupleInvisible should be impossible + * because we're waiting for updated row versions, + * and would already have errored out if the first + * version is invisible. + * + * TableTupleSelfModified should be impossible, as + * we'd otherwise should have hit the + * TableTupleSelfModified case in response to + * table_delete above. + * + * TableTupleUpdated should be impossible, because + * we're locking the latest version via + * TUPLE_LOCK_FLAG_FIND_LAST_VERSION. + */ + elog(ERROR, "unexpected table_lock_tuple status: %u", result); + return NULL; + } + + Assert(false); + break; + } + + case TableTupleDeleted: if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be deleted was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *my_epqslot; - - my_epqslot = EvalPlanQual(estate, - epqstate, - resultRelationDesc, - resultRelInfo->ri_RangeTableIndex, - LockTupleExclusive, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(my_epqslot)) - { - *tupleid = hufd.ctid; - - /* - * If requested, skip delete and pass back the updated - * row. - */ - if (epqreturnslot) - { - *epqreturnslot = my_epqslot; - return NULL; - } - else - goto ldelete; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; default: - elog(ERROR, "unrecognized heap_delete status: %u", result); + elog(ERROR, "unrecognized table_delete status: %u", result); return NULL; } @@ -842,7 +881,7 @@ ldelete:; deltuple->t_self = *tupleid; if (!heap_fetch(resultRelationDesc, SnapshotAny, - deltuple, &buffer, false, NULL)) + deltuple, &buffer, NULL)) elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); ExecStorePinnedBufferHeapTuple(deltuple, slot, buffer); @@ -897,11 +936,10 @@ ExecUpdate(ModifyTableState *mtstate, EState *estate, bool canSetTag) { - HeapTuple updatetuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; - HTSU_Result result; - HeapUpdateFailureData hufd; + TM_Result result; + TM_FailureData tmfd; List *recheckIndexes = NIL; TupleConversionMap *saved_tcs_map = NULL; @@ -925,7 +963,7 @@ ExecUpdate(ModifyTableState *mtstate, { if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, tupleid, oldtuple, slot)) - return NULL; /* "do nothing" */ + return NULL; /* "do nothing" */ } /* INSTEAD OF ROW UPDATE Triggers */ @@ -934,7 +972,7 @@ ExecUpdate(ModifyTableState *mtstate, { if (!ExecIRUpdateTriggers(estate, resultRelInfo, oldtuple, slot)) - return NULL; /* "do nothing" */ + return NULL; /* "do nothing" */ } else if (resultRelInfo->ri_FdwRoutine) { @@ -960,6 +998,7 @@ ExecUpdate(ModifyTableState *mtstate, { LockTupleMode lockmode; bool partition_constraint_failed; + bool update_indexes; /* * Constraints might reference the tableoid column, so (re-)initialize @@ -973,11 +1012,14 @@ ExecUpdate(ModifyTableState *mtstate, * If we generate a new candidate tuple after EvalPlanQual testing, we * must loop back here and recheck any RLS policies and constraints. * (We don't need to redo triggers, however. If there are any BEFORE - * triggers then trigger.c will have done heap_lock_tuple to lock the + * triggers then trigger.c will have done table_lock_tuple to lock the * correct tuple, so there's no need to do them again.) */ lreplace:; + /* ensure slot is independent, consider e.g. EPQ */ + ExecMaterializeSlot(slot); + /* * If partition constraint fails, this row might get moved to another * partition, in which case we should check the RLS CHECK policy just @@ -1145,18 +1187,16 @@ lreplace:; * needed for referential integrity updates in transaction-snapshot * mode transactions. */ - updatetuple = ExecFetchSlotHeapTuple(slot, true, NULL); - result = heap_update(resultRelationDesc, tupleid, - updatetuple, - estate->es_output_cid, - estate->es_crosscheck_snapshot, - true /* wait for commit */ , - &hufd, &lockmode); - ItemPointerCopy(&updatetuple->t_self, &slot->tts_tid); + result = table_update(resultRelationDesc, tupleid, slot, + estate->es_output_cid, + estate->es_snapshot, + estate->es_crosscheck_snapshot, + true /* wait for commit */ , + &tmfd, &lockmode, &update_indexes); switch (result) { - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * The target tuple was already updated or deleted by the @@ -1181,7 +1221,7 @@ lreplace:; * can re-execute the UPDATE (assuming it can figure out how) * and then return NULL to cancel the outer update. */ - if (hufd.cmax != estate->es_output_cid) + if (tmfd.cmax != estate->es_output_cid) ereport(ERROR, (errcode(ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION), errmsg("tuple to be updated was already modified by an operation triggered by the current command"), @@ -1190,64 +1230,80 @@ lreplace:; /* Else, already updated by self; nothing to do */ return NULL; - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: break; - case HeapTupleUpdated: + case TableTupleUpdated: + { + TupleTableSlot *inputslot; + TupleTableSlot *epqslot; + + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent update"))); + + /* + * Already know that we're going to need to do EPQ, so + * fetch tuple directly into the right slot. + */ + EvalPlanQualBegin(epqstate, estate); + inputslot = EvalPlanQualSlot(epqstate, resultRelationDesc, + resultRelInfo->ri_RangeTableIndex); + + result = table_lock_tuple(resultRelationDesc, tupleid, + estate->es_snapshot, + inputslot, estate->es_output_cid, + lockmode, LockWaitBlock, + TUPLE_LOCK_FLAG_FIND_LAST_VERSION, + &tmfd); + + switch (result) + { + case TableTupleMayBeModified: + Assert(tmfd.traversed); + + epqslot = EvalPlanQual(estate, + epqstate, + resultRelationDesc, + resultRelInfo->ri_RangeTableIndex, + inputslot); + if (TupIsNull(epqslot)) + /* Tuple not passing quals anymore, exiting... */ + return NULL; + + slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); + goto lreplace; + + case TableTupleDeleted: + /* tuple already deleted; nothing to do */ + return NULL; + + default: + /* see table_lock_tuple call in ExecDelete() */ + elog(ERROR, "unexpected table_lock_tuple status: %u", result); + return NULL; + } + } + + break; + + case TableTupleDeleted: if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("could not serialize access due to concurrent update"))); - if (ItemPointerIndicatesMovedPartitions(&hufd.ctid)) - ereport(ERROR, - (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), - errmsg("tuple to be updated was already moved to another partition due to concurrent update"))); - - if (!ItemPointerEquals(tupleid, &hufd.ctid)) - { - TupleTableSlot *epqslot; - - epqslot = EvalPlanQual(estate, - epqstate, - resultRelationDesc, - resultRelInfo->ri_RangeTableIndex, - lockmode, - &hufd.ctid, - hufd.xmax); - if (!TupIsNull(epqslot)) - { - *tupleid = hufd.ctid; - slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); - goto lreplace; - } - } + errmsg("could not serialize access due to concurrent delete"))); /* tuple already deleted; nothing to do */ return NULL; default: - elog(ERROR, "unrecognized heap_update status: %u", result); + elog(ERROR, "unrecognized table_update status: %u", result); return NULL; } - /* - * Note: instead of having to update the old index tuples associated - * with the heap tuple, all we do is form and insert new index tuples. - * This is because UPDATEs are actually DELETEs and INSERTs, and index - * tuple deletion is done later by VACUUM (see notes in ExecDelete). - * All we do here is insert new index tuples. -cim 9/27/89 - */ - - /* - * insert index entries for tuple - * - * Note: heap_update returns the tid (location) of the new tuple in - * the t_self field. - * - * If it's a HOT update, we mustn't insert new index entries. - */ - if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(updatetuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(updatetuple->t_self), - estate, false, NULL, NIL); + /* insert index entries for tuple if necessary */ + if (resultRelInfo->ri_NumIndices > 0 && update_indexes) + recheckIndexes = ExecInsertIndexTuples(slot, estate, false, NULL, NIL); } if (canSetTag) @@ -1306,11 +1362,12 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, Relation relation = resultRelInfo->ri_RelationDesc; ExprState *onConflictSetWhere = resultRelInfo->ri_onConflict->oc_WhereClause; TupleTableSlot *existing = resultRelInfo->ri_onConflict->oc_Existing; - HeapTupleData tuple; - HeapUpdateFailureData hufd; + TM_FailureData tmfd; LockTupleMode lockmode; - HTSU_Result test; - Buffer buffer; + TM_Result test; + Datum xminDatum; + TransactionId xmin; + bool isnull; /* Determine lock mode to use */ lockmode = ExecUpdateLockMode(estate, resultRelInfo); @@ -1321,17 +1378,18 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * previous conclusion that the tuple is conclusively committed is not * true anymore. */ - tuple.t_self = *conflictTid; - test = heap_lock_tuple(relation, &tuple, estate->es_output_cid, - lockmode, LockWaitBlock, false, &buffer, - &hufd); + test = table_lock_tuple(relation, conflictTid, + estate->es_snapshot, + existing, estate->es_output_cid, + lockmode, LockWaitBlock, 0, + &tmfd); switch (test) { - case HeapTupleMayBeUpdated: + case TableTupleMayBeModified: /* success! */ break; - case HeapTupleInvisible: + case TableTupleInvisible: /* * This can occur when a just inserted tuple is updated again in @@ -1339,7 +1397,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * conflicting key values are inserted. * * This is somewhat similar to the ExecUpdate() - * HeapTupleSelfUpdated case. We do not want to proceed because + * TableTupleSelfModified case. We do not want to proceed because * it would lead to the same row being updated a second time in * some unspecified order, and in contrast to plain UPDATEs * there's no historical behavior to break. @@ -1349,7 +1407,13 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * that for SQL MERGE, an exception must be raised in the event of * an attempt to update the same row twice. */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple.t_data))) + xminDatum = slot_getsysattr(existing, + MinTransactionIdAttributeNumber, + &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + + if (TransactionIdIsCurrentTransactionId(xmin)) ereport(ERROR, (errcode(ERRCODE_CARDINALITY_VIOLATION), errmsg("ON CONFLICT DO UPDATE command cannot affect row a second time"), @@ -1359,7 +1423,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, elog(ERROR, "attempted to lock invisible tuple"); break; - case HeapTupleSelfUpdated: + case TableTupleSelfModified: /* * This state should never be reached. As a dirty snapshot is used @@ -1369,7 +1433,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, elog(ERROR, "unexpected self-updated tuple"); break; - case HeapTupleUpdated: + case TableTupleUpdated: if (IsolationUsesXactSnapshot()) ereport(ERROR, (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), @@ -1381,7 +1445,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * be lock is moved to another partition due to concurrent update * of the partition key. */ - Assert(!ItemPointerIndicatesMovedPartitions(&hufd.ctid)); + Assert(!ItemPointerIndicatesMovedPartitions(&tmfd.ctid)); /* * Tell caller to try again from the very start. @@ -1390,11 +1454,20 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * loop here, as the new version of the row might not conflict * anymore, or the conflicting tuple has actually been deleted. */ - ReleaseBuffer(buffer); + ExecClearTuple(existing); + return false; + + case TableTupleDeleted: + if (IsolationUsesXactSnapshot()) + ereport(ERROR, + (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), + errmsg("could not serialize access due to concurrent delete"))); + + ExecClearTuple(existing); return false; default: - elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); + elog(ERROR, "unrecognized table_lock_tuple status: %u", test); } /* Success, the tuple is locked. */ @@ -1412,10 +1485,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, * snapshot. This is in line with the way UPDATE deals with newer tuple * versions. */ - ExecCheckHeapTupleVisible(estate, &tuple, buffer); - - /* Store target's existing tuple in the state's dedicated slot */ - ExecStorePinnedBufferHeapTuple(&tuple, existing, buffer); + ExecCheckHeapTupleVisible(estate, relation, existing); /* * Make tuple and any needed join variables available to ExecQual and @@ -1462,7 +1532,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, /* * Note that it is possible that the target tuple has been modified in - * this session, after the above heap_lock_tuple. We choose to not error + * this session, after the above table_lock_tuple. We choose to not error * out in that case, in line with ExecUpdate's treatment of similar cases. * This can happen if an UPDATE is triggered from within ExecQual(), * ExecWithCheckOptions() or ExecProject() above, e.g. by selecting from a @@ -1470,7 +1540,7 @@ ExecOnConflictUpdate(ModifyTableState *mtstate, */ /* Execute UPDATE with projection */ - *returning = ExecUpdate(mtstate, &tuple.t_self, NULL, + *returning = ExecUpdate(mtstate, conflictTid, NULL, resultRelInfo->ri_onConflict->oc_ProjSlot, planSlot, &mtstate->mt_epqstate, mtstate->ps.state, diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 08872ef9b4f..0e6a0748c8c 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -376,7 +376,7 @@ TidNext(TidScanState *node) if (node->tss_isCurrentOf) heap_get_latest_tid(heapRelation, snapshot, &tuple->t_self); - if (heap_fetch(heapRelation, snapshot, tuple, &buffer, false, NULL)) + if (heap_fetch(heapRelation, snapshot, tuple, &buffer, NULL)) { /* * Store the scanned tuple in the scan tuple slot of the scan diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index eb9e160bfd9..505fce96b0e 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -19,6 +19,7 @@ #include "access/sdir.h" #include "access/skey.h" #include "access/table.h" /* for backward compatibility */ +#include "access/tableam.h" #include "nodes/lockoptions.h" #include "nodes/primnodes.h" #include "storage/bufpage.h" @@ -28,39 +29,16 @@ /* "options" flag bits for heap_insert */ -#define HEAP_INSERT_SKIP_WAL 0x0001 -#define HEAP_INSERT_SKIP_FSM 0x0002 -#define HEAP_INSERT_FROZEN 0x0004 -#define HEAP_INSERT_SPECULATIVE 0x0008 -#define HEAP_INSERT_NO_LOGICAL 0x0010 +#define HEAP_INSERT_SKIP_WAL TABLE_INSERT_SKIP_WAL +#define HEAP_INSERT_SKIP_FSM TABLE_INSERT_SKIP_FSM +#define HEAP_INSERT_FROZEN TABLE_INSERT_FROZEN +#define HEAP_INSERT_NO_LOGICAL TABLE_INSERT_NO_LOGICAL +#define HEAP_INSERT_SPECULATIVE 0x0010 typedef struct BulkInsertStateData *BulkInsertState; #define MaxLockTupleMode LockTupleExclusive -/* - * When heap_update, heap_delete, or heap_lock_tuple fail because the target - * tuple is already outdated, they fill in this struct to provide information - * to the caller about what happened. - * ctid is the target's ctid link: it is the same as the target's TID if the - * target was deleted, or the location of the replacement tuple if the target - * was updated. - * xmax is the outdating transaction's XID. If the caller wants to visit the - * replacement tuple, it must check that this matches before believing the - * replacement is really a match. - * cmax is the outdating command's CID, but only when the failure code is - * HeapTupleSelfUpdated (i.e., something in the current transaction outdated - * the tuple); otherwise cmax is zero. (We make this restriction because - * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other - * transactions.) - */ -typedef struct HeapUpdateFailureData -{ - ItemPointerData ctid; - TransactionId xmax; - CommandId cmax; -} HeapUpdateFailureData; - /* * Descriptor for heap table scans. */ @@ -150,8 +128,7 @@ extern bool heap_getnextslot(TableScanDesc sscan, ScanDirection direction, struct TupleTableSlot *slot); extern bool heap_fetch(Relation relation, Snapshot snapshot, - HeapTuple tuple, Buffer *userbuf, bool keep_buf, - Relation stats_relation); + HeapTuple tuple, Buffer *userbuf, Relation stats_relation); extern bool heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, Snapshot snapshot, HeapTuple heapTuple, bool *all_dead, bool first_call); @@ -170,19 +147,20 @@ extern void heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate); extern void heap_multi_insert(Relation relation, HeapTuple *tuples, int ntuples, CommandId cid, int options, BulkInsertState bistate); -extern HTSU_Result heap_delete(Relation relation, ItemPointer tid, +extern TM_Result heap_delete(Relation relation, ItemPointer tid, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, bool changingPart); -extern void heap_finish_speculative(Relation relation, HeapTuple tuple); -extern void heap_abort_speculative(Relation relation, HeapTuple tuple); -extern HTSU_Result heap_update(Relation relation, ItemPointer otid, + struct TM_FailureData *tmfd, bool changingPart); +extern void heap_finish_speculative(Relation relation, ItemPointer tid); +extern void heap_abort_speculative(Relation relation, ItemPointer tid); +extern TM_Result heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, - HeapUpdateFailureData *hufd, LockTupleMode *lockmode); -extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, + struct TM_FailureData *tmfd, LockTupleMode *lockmode); +extern TM_Result heap_lock_tuple(Relation relation, ItemPointer tid, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, - bool follow_update, - Buffer *buffer, HeapUpdateFailureData *hufd); + bool follow_update, HeapTuple tuple, + Buffer *buffer, struct TM_FailureData *tmfd); + extern void heap_inplace_update(Relation relation, HeapTuple tuple); extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId relfrozenxid, TransactionId relminmxid, @@ -223,7 +201,7 @@ extern void heap_vacuum_rel(Relation onerel, /* in heap/heapam_visibility.c */ extern bool HeapTupleSatisfiesVisibility(HeapTuple stup, Snapshot snapshot, Buffer buffer); -extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid, +extern TM_Result HeapTupleSatisfiesUpdate(HeapTuple stup, CommandId curcid, Buffer buffer); extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple stup, TransactionId OldestXmin, Buffer buffer); diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 50b8ab93539..b257e9a2aa5 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -27,6 +27,73 @@ extern char *default_table_access_method; extern bool synchronize_seqscans; +struct BulkInsertStateData; + + +/* + * Result codes for table_{update,delete,lock}_tuple, and for visibility + * routines inside table AMs. + */ +typedef enum TM_Result +{ + /* Signals that the action succeeded (i.e. update/delete performed) */ + TableTupleMayBeModified, + + /* The affected tuple wasn't visible to the relevant snapshot */ + TableTupleInvisible, + + /* The affected tuple was already modified by the calling backend */ + TableTupleSelfModified, + + /* The affected tuple was updated by another transaction */ + TableTupleUpdated, + + /* The affected tuple was deleted by another transaction */ + TableTupleDeleted, + + /* + * The affected tuple is currently being modified by another session. This + * will only be returned if (update/delete/lock)_tuple are instructed not + * to wait. + */ + TableTupleBeingModified, + + /* lock couldn't be acquired, action skipped. Only used by lock_tuple */ + TableTupleWouldBlock +} TM_Result; + + +/* + * When table_update, table_delete, or table_lock_tuple fail because the target + * tuple is already outdated, they fill in this struct to provide information + * to the caller about what happened. + * ctid is the target's ctid link: it is the same as the target's TID if the + * target was deleted, or the location of the replacement tuple if the target + * was updated. + * xmax is the outdating transaction's XID. If the caller wants to visit the + * replacement tuple, it must check that this matches before believing the + * replacement is really a match. + * cmax is the outdating command's CID, but only when the failure code is + * TableTupleSelfModified (i.e., something in the current transaction outdated + * the tuple); otherwise cmax is zero. (We make this restriction because + * HeapTupleHeaderGetCmax doesn't work for tuples outdated in other + * transactions.) + */ +typedef struct TM_FailureData +{ + ItemPointerData ctid; + TransactionId xmax; + CommandId cmax; + bool traversed; +} TM_FailureData; + +/* "options" flag bits for heap_insert */ +#define TABLE_INSERT_SKIP_WAL 0x0001 +#define TABLE_INSERT_SKIP_FSM 0x0002 +#define TABLE_INSERT_FROZEN 0x0004 +#define TABLE_INSERT_NO_LOGICAL 0x0008 + + /* * API struct for a table AM. Note this must be allocated in a * server-lifetime manner, typically as a static const struct, which then gets @@ -200,6 +267,62 @@ typedef struct TableAmRoutine TupleTableSlot *slot, Snapshot snapshot); + /* ------------------------------------------------------------------------ + * Manipulations of physical tuples. + * ------------------------------------------------------------------------ + */ + + /* see table_insert() for reference about parameters */ + void (*tuple_insert) (Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate); + + /* see table_insert() for reference about parameters */ + void (*tuple_insert_speculative) (Relation rel, + TupleTableSlot *slot, + CommandId cid, + int options, + struct BulkInsertStateData *bistate, + uint32 specToken); + + /* see table_insert() for reference about parameters */ + void (*tuple_complete_speculative) (Relation rel, + TupleTableSlot *slot, + uint32 specToken, + bool succeeded); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_delete) (Relation rel, + ItemPointer tid, + CommandId cid, + Snapshot snapshot, + Snapshot crosscheck, + bool wait, + TM_FailureData *tmfd, + bool changingPart); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_update) (Relation rel, + ItemPointer otid, + TupleTableSlot *slot, + CommandId cid, + Snapshot snapshot, + Snapshot crosscheck, + bool wait, + TM_FailureData *tmfd, + LockTupleMode *lockmode, + bool *update_indexes); + + /* see table_insert() for reference about parameters */ + TM_Result (*tuple_lock) (Relation rel, + ItemPointer tid, + Snapshot snapshot, + TupleTableSlot *slot, + CommandId cid, + LockTupleMode mode, + LockWaitPolicy wait_policy, + uint8 flags, + TM_FailureData *tmfd); + } TableAmRoutine; @@ -487,6 +610,230 @@ table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot, Snapshot snap } +/* ---------------------------------------------------------------------------- + * Functions for manipulations of physical tuples. + * ---------------------------------------------------------------------------- + */ + +/* + * Insert a tuple from a slot into table AM routine. + * + * The options bitmask allows to specify options that allow to change the + * behaviour of the AM. Several options might be ignored by AMs not supporting + * them. + * + * If the TABLE_INSERT_SKIP_WAL option is specified, the new tuple will not + * necessarily logged to WAL, even for a non-temp relation. It is the AMs + * choice whether this optimization is supported. + * + * If the TABLE_INSERT_SKIP_FSM option is specified, AMs are free to not reuse + * free space in the relation. This can save some cycles when we know the + * relation is new and doesn't contain useful amounts of free space. It's + * commonly passed directly to RelationGetBufferForTuple, see for more info. + * + * TABLE_INSERT_FROZEN should only be specified for inserts into + * relfilenodes created during the current subtransaction and when + * there are no prior snapshots or pre-existing portals open. + * This causes rows to be frozen, which is an MVCC violation and + * requires explicit options chosen by user. + * + * TABLE_INSERT_NO_LOGICAL force-disables the emitting of logical decoding + * information for the tuple. This should solely be used during table rewrites + * where RelationIsLogicallyLogged(relation) is not yet accurate for the new + * relation. + * + * Note that most of these options will be applied when inserting into the + * heap's TOAST table, too, if the tuple requires any out-of-line data + * + * + * The BulkInsertState object (if any; bistate can be NULL for default + * behavior) is also just passed through to RelationGetBufferForTuple. + * + * On return the slot's tts_tid and tts_tableOid are updated to reflect the + * insertion. But note that any toasting of fields within the slot is NOT + * reflected in the slots contents. + */ +static inline void +table_insert(Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate) +{ + rel->rd_tableam->tuple_insert(rel, slot, cid, options, + bistate); +} + +/* + * Perform a "speculative insertion". These can be backed out afterwards + * without aborting the whole transaction. Other sessions can wait for the + * speculative insertion to be confirmed, turning it into a regular tuple, or + * aborted, as if it never existed. Speculatively inserted tuples behave as + * "value locks" of short duration, used to implement INSERT .. ON CONFLICT. + * + * A transaction having performed a speculative insertion has to either abort, + * or finish the speculative insertion with + * table_complete_speculative(succeeded = ...). + */ +static inline void +table_insert_speculative(Relation rel, TupleTableSlot *slot, CommandId cid, + int options, struct BulkInsertStateData *bistate, uint32 specToken) +{ + rel->rd_tableam->tuple_insert_speculative(rel, slot, cid, options, + bistate, specToken); +} + +/* + * Complete "speculative insertion" started in the same transaction. If + * succeeded is true, the tuple is fully inserted, if false, it's removed. + */ +static inline void +table_complete_speculative(Relation rel, TupleTableSlot *slot, uint32 specToken, + bool succeeded) +{ + return rel->rd_tableam->tuple_complete_speculative(rel, slot, specToken, + succeeded); +} + +/* + * Delete a tuple. + * + * NB: do not call this directly unless prepared to deal with + * concurrent-update conditions. Use simple_table_delete instead. + * + * Input parameters: + * relation - table to be modified (caller must hold suitable lock) + * tid - TID of tuple to be deleted + * cid - delete command ID (used for visibility test, and stored into + * cmax if successful) + * crosscheck - if not InvalidSnapshot, also check tuple against this + * wait - true if should wait for any conflicting update to commit/abort + * Output parameters: + * tmfd - filled in failure cases (see below) + * changingPart - true iff the tuple is being moved to another partition + * table due to an update of the partition key. Otherwise, false. + * + * Normal, successful return value is TableTupleMayBeModified, which + * actually means we did delete it. Failure return codes are + * TableTupleSelfModified, TableTupleUpdated, or TableTupleBeingModified + * (the last only possible if wait == false). + * + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax, and, if possible, and, if possible, t_cmax. See comments for + * struct TM_FailureData for additional info. + */ +static inline TM_Result +table_delete(Relation rel, ItemPointer tid, CommandId cid, + Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, bool changingPart) +{ + return rel->rd_tableam->tuple_delete(rel, tid, cid, + snapshot, crosscheck, + wait, tmfd, changingPart); +} + +/* + * Update a tuple. + * + * NB: do not call this directly unless you are prepared to deal with + * concurrent-update conditions. Use simple_table_update instead. + * + * Input parameters: + * relation - table to be modified (caller must hold suitable lock) + * otid - TID of old tuple to be replaced + * newtup - newly constructed tuple data to store + * cid - update command ID (used for visibility test, and stored into + * cmax/cmin if successful) + * crosscheck - if not InvalidSnapshot, also check old tuple against this + * wait - true if should wait for any conflicting update to commit/abort + * Output parameters: + * tmfd - filled in failure cases (see below) + * lockmode - filled with lock mode acquired on tuple + * update_indexes - in success cases this is set to true if new index entries + * are required for this tuple + * + * Normal, successful return value is TableTupleMayBeModified, which + * actually means we *did* update it. Failure return codes are + * TableTupleSelfModified, TableTupleUpdated, or TableTupleBeingModified + * (the last only possible if wait == false). + * + * On success, the header fields of *newtup are updated to match the new + * stored tuple; in particular, newtup->t_self is set to the TID where the + * new tuple was inserted, and its HEAP_ONLY_TUPLE flag is set iff a HOT + * update was done. However, any TOAST changes in the new tuple's + * data are not reflected into *newtup. + * + * In the failure cases, the routine fills *tmfd with the tuple's t_ctid, + * t_xmax, and, if possible, t_cmax. See comments for struct TM_FailureData + * for additional info. + */ +static inline TM_Result +table_update(Relation rel, ItemPointer otid, TupleTableSlot *slot, + CommandId cid, Snapshot snapshot, Snapshot crosscheck, bool wait, + TM_FailureData *tmfd, LockTupleMode *lockmode, + bool *update_indexes) +{ + return rel->rd_tableam->tuple_update(rel, otid, slot, + cid, snapshot, crosscheck, + wait, tmfd, + lockmode, update_indexes); +} + +/* + * Lock a tuple in the specified mode. + * + * Input parameters: + * relation: relation containing tuple (caller must hold suitable lock) + * tid: TID of tuple to lock + * snapshot: snapshot to use for visibility determinations + * cid: current command ID (used for visibility test, and stored into + * tuple's cmax if lock is successful) + * mode: lock mode desired + * wait_policy: what to do if tuple lock is not available + * flags: + * If TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS, follow the update chain to + * also lock descendant tuples if lock modes don't conflict. + * If TUPLE_LOCK_FLAG_FIND_LAST_VERSION, update chain and lock lastest + * version. + * + * Output parameters: + * *slot: contains the target tuple + * *tmfd: filled in failure cases (see below) + * + * Function result may be: + * TableTupleMayBeModified: lock was successfully acquired + * TableTupleInvisible: lock failed because tuple was never visible to us + * TableTupleSelfModified: lock failed because tuple updated by self + * TableTupleUpdated: lock failed because tuple updated by other xact + * TableTupleDeleted: lock failed because tuple deleted by other xact + * TableTupleWouldBlock: lock couldn't be acquired and wait_policy is skip + * + * In the failure cases other than TableTupleInvisible, the routine fills + * *tmfd with the tuple's t_ctid, t_xmax, and, if possible, t_cmax. See + * comments for struct TM_FailureData for additional info. + */ +static inline TM_Result +table_lock_tuple(Relation rel, ItemPointer tid, Snapshot snapshot, + TupleTableSlot *slot, CommandId cid, LockTupleMode mode, + LockWaitPolicy wait_policy, uint8 flags, + TM_FailureData *tmfd) +{ + return rel->rd_tableam->tuple_lock(rel, tid, snapshot, slot, + cid, mode, wait_policy, + flags, tmfd); +} + + +/* ---------------------------------------------------------------------------- + * Functions to make modifications a bit simpler. + * ---------------------------------------------------------------------------- + */ + +extern void simple_table_insert(Relation rel, TupleTableSlot *slot); +extern void simple_table_delete(Relation rel, ItemPointer tid, + Snapshot snapshot); +extern void simple_table_update(Relation rel, ItemPointer otid, + TupleTableSlot *slot, Snapshot snapshot, + bool *update_indexes); + + /* ---------------------------------------------------------------------------- * Helper functions to implement parallel scans for block oriented AMs. * ---------------------------------------------------------------------------- diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 9003f2ce583..ceacd1c6370 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -195,12 +195,7 @@ extern LockTupleMode ExecUpdateLockMode(EState *estate, ResultRelInfo *relinfo); extern ExecRowMark *ExecFindRowMark(EState *estate, Index rti, bool missing_ok); extern ExecAuxRowMark *ExecBuildAuxRowMark(ExecRowMark *erm, List *targetlist); extern TupleTableSlot *EvalPlanQual(EState *estate, EPQState *epqstate, - Relation relation, Index rti, LockTupleMode lockmode, - ItemPointer tid, TransactionId priorXmax); -extern bool EvalPlanQualFetch(EState *estate, Relation relation, - LockTupleMode lockmode, LockWaitPolicy wait_policy, - ItemPointer tid, TransactionId priorXmax, - TupleTableSlot *slot); + Relation relation, Index rti, TupleTableSlot *testslot); extern void EvalPlanQualInit(EPQState *epqstate, EState *estate, Plan *subplan, List *auxrowmarks, int epqParam); extern void EvalPlanQualSetPlan(EPQState *epqstate, @@ -569,9 +564,8 @@ extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relIn */ extern void ExecOpenIndices(ResultRelInfo *resultRelInfo, bool speculative); extern void ExecCloseIndices(ResultRelInfo *resultRelInfo); -extern List *ExecInsertIndexTuples(TupleTableSlot *slot, ItemPointer tupleid, - EState *estate, bool noDupErr, bool *specConflict, - List *arbiterIndexes); +extern List *ExecInsertIndexTuples(TupleTableSlot *slot, EState *estate, bool noDupErr, + bool *specConflict, List *arbiterIndexes); extern bool ExecCheckIndexConstraints(TupleTableSlot *slot, EState *estate, ItemPointer conflictTid, List *arbiterIndexes); extern void check_exclusion_constraint(Relation heap, Relation index, diff --git a/src/include/nodes/lockoptions.h b/src/include/nodes/lockoptions.h index 8e8ccff43ca..d6b1160ab4b 100644 --- a/src/include/nodes/lockoptions.h +++ b/src/include/nodes/lockoptions.h @@ -58,4 +58,9 @@ typedef enum LockTupleMode LockTupleExclusive } LockTupleMode; +/* Follow tuples whose update is in progress if lock modes don't conflict */ +#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS (1 << 0) +/* Follow update chain and lock lastest version of tuple */ +#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION (1 << 1) + #endif /* LOCKOPTIONS_H */ diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h index e7ea5cf7b56..7bf7cad5727 100644 --- a/src/include/utils/snapshot.h +++ b/src/include/utils/snapshot.h @@ -184,17 +184,4 @@ typedef struct SnapshotData XLogRecPtr lsn; /* position in the WAL stream when taken */ } SnapshotData; -/* - * Result codes for HeapTupleSatisfiesUpdate. - */ -typedef enum -{ - HeapTupleMayBeUpdated, - HeapTupleInvisible, - HeapTupleSelfUpdated, - HeapTupleUpdated, - HeapTupleBeingUpdated, - HeapTupleWouldBlock /* can be returned by heap_tuple_lock */ -} HTSU_Result; - #endif /* SNAPSHOT_H */ diff --git a/src/test/isolation/expected/partition-key-update-1.out b/src/test/isolation/expected/partition-key-update-1.out index 37fe6a7b277..a632d7f7bad 100644 --- a/src/test/isolation/expected/partition-key-update-1.out +++ b/src/test/isolation/expected/partition-key-update-1.out @@ -15,7 +15,7 @@ step s1u: UPDATE foo SET a=2 WHERE a=1; step s2d: DELETE FROM foo WHERE a=1; step s1c: COMMIT; step s2d: <... completed> -error in steps s1c s2d: ERROR: tuple to be deleted was already moved to another partition due to concurrent update +error in steps s1c s2d: ERROR: tuple to be locked was already moved to another partition due to concurrent update step s2c: COMMIT; starting permutation: s1b s2b s2d s1u s2c s1c diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index b301bce4b1b..0015fc0ead9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -943,7 +943,6 @@ HSParser HSpool HStore HTAB -HTSU_Result HTSV_Result HV Hash @@ -982,7 +981,6 @@ HeapTupleData HeapTupleFields HeapTupleHeader HeapTupleHeaderData -HeapUpdateFailureData HistControl HotStandbyState I32 @@ -2282,6 +2280,8 @@ TBMSharedIteratorState TBMStatus TBlockState TIDBitmap +TM_FailureData +TM_Result TOKEN_DEFAULT_DACL TOKEN_INFORMATION_CLASS TOKEN_PRIVILEGES -- 2.21.0.dirty --qw3ipai4e5xhdytn--