Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wdz1z-004TWB-1N for pgsql-bugs@arkaria.postgresql.org; Sun, 28 Jun 2026 23:35:19 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wdz1w-00HTHo-19 for pgsql-bugs@arkaria.postgresql.org; Sun, 28 Jun 2026 23:35:16 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wdz1v-00HTHe-2h for pgsql-bugs@lists.postgresql.org; Sun, 28 Jun 2026 23:35:15 +0000 Received: from mail-oi1-x234.google.com ([2607:f8b0:4864:20::234]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wdz1t-00000000fnl-1fxO for pgsql-bugs@lists.postgresql.org; Sun, 28 Jun 2026 23:35:14 +0000 Received: by mail-oi1-x234.google.com with SMTP id 5614622812f47-48d6c621a67so1392278b6e.3 for ; Sun, 28 Jun 2026 16:35:13 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782689712; cv=none; d=google.com; s=arc-20260327; b=pk//SOVc6iB51XMnoSfjyaBF5PO/k19MSsQGOaczPAtLByX7gS5zKcMG2X1ZHYXoc1 dCG+oSc5AIQaVK9D47NOjYsJYiLgWz0q8DdecV8egYtBR4y9Eil1Gi9mM8L6UiglpW0P HGNSuP0zWN+GHmXSdkf28pHYDq5wdjZpUTFCGN7lkOYaLoNqiFyTCwf6c4JC005n9Kxo e99vm7WpaD2MfFMlefvRctaHhV8V49ncXXH1c/KUsZpEbrsUeNqhyrx2e6dwRVAFULoN +o2FLFUkByFVYVZ8poKF5X6I4M1RC2/oW0KoVmNmPwLHj4opb98nY+EMTI7NGrI3CLXd pj0A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=4aAaXkqF5RtG8bGEjRfsE5c01l9PBA2SA7LWUzwwFrw=; fh=agiGe9t3beq/wMJpMtFYIZg1PlH5tM6bnaJMNDMXMNE=; b=AhCt91DTZwNKKSNzVSmXAWshf4paOc315NZRWaqZdWTT5M1zHE4WDq76ZYYtrkuNft T4nidXMo+mwWFq7TXAVEI+u+HtJg9wheTUt0dErojNQ0+T1RAF40HLnrzefp/yjTNE5J LsxQkpaL5GRwvBqJk4huBewTCNYNMnJrVPNNN908QeRj32ZICVnOopdUq8Sm1roZZwi7 pANydLuGIqA5abkWe34xyFNYfxE57p7bv+gYU8yySDZzd/XlHKIxrNfIcQb8zF+Jcx+F 5uHLmwrAenD1n4aqPNstsUeU78yw3wWtnAhHgXxP1EFB7PvisanG3XfT3BcdBCnoIB3G RZSA==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782689712; x=1783294512; darn=lists.postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=4aAaXkqF5RtG8bGEjRfsE5c01l9PBA2SA7LWUzwwFrw=; b=k0vdqN8GSfKE/zjzUWlrv8mc8RVZeUARsAR2LB3/1+9dBC+FTC1oPOr6A5/VVJEKtu 1s//S/cTxHzYj0psYyaLYM8eZFdwFgTAeEdX3ugk2K+ScJmyNAt4QYr642gTnzKnO0/i yvFj/sL5Tj2psg8nH++2H6zHRYZ+SNPDOA05fUeoQjIEOiqHz4drTqQUNIZO+LhbZwS6 z1zRpMLepVL7rAyR6O+kbD/c+w6ShAIwj6Woj75cIqj8swOCbRPYZcQ9bf5qtkfk7puL S/WuhlE88CeWEds5JkwaZCbxejJThG8YBgLp0q85QPBU6D3LAa835j6Z91D/9OMY1yaw nZIg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782689712; x=1783294512; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=4aAaXkqF5RtG8bGEjRfsE5c01l9PBA2SA7LWUzwwFrw=; b=sR5uM3yzi7Bib4smHvESbIjtT6t1apaltaU8g4k0Ynq5n1h9kce5MN/FTBuPjc6NBf +ReEv31AtUU1jWXcIR9HJShjwIe4uKDozChMfJmKnwNN9HPD9gn6fwL2wZZpch3XKE5D NIZ3qFJqvhvyH5tehgLq6O8+a/ODSQuV02qikK132emhXa8zkIXvWspD7xDc74lGbKDN Qw4T2ZmmQ59l2FyxtGsBQmsZhQVN5wd3i7BUFjvSUYoJ5mvLlfvrB2Eo89QAQy6j4Hy3 A17a38hIqZ5snIaFRDyh1e1J/O86pcBJkDW3fr6QW+GNSO9f05E5TK3MfowvjcmSH1Ti RQaw== X-Forwarded-Encrypted: i=1; AFNElJ9gkIXDa5KW3xIsRDIGXnH4vvpNM4zzQ1JpF6hSAvSPj7dVLPmGEcY+Z4kuQtBj/OX7asU45e9+2Yw6@lists.postgresql.org X-Gm-Message-State: AOJu0YyvJzqELCZKEexzDqWwav0irNuYnnQtItIgC+xTBXQ3jm0FOVYS BdFHB3Bbg+8wyuA9nI5rYfpaLXIZVEDIDc/P3J5IXtj7rXUpC8Z/nfc+QAnBPwrsJezdc45/n2Y OVhLF/cUH1SW+8c6sW8thibi8PhxtFSE= X-Gm-Gg: AfdE7clowzQ1ay9UN7STcVU/66Ym1skb9mERFaS0FH7Ju/vsRWrc7AnKQ3/3kwwqZEe zmMWWLkKAFUFyeIMnh37NHrNSIIJS+3sTXwP1fobxLskyv3MzY0deOL93f2jPbLZpvi/Lv9jI5l DEzzzScAUjhwAHXBnSlL/t38VXZqYcqm+yH8SdXU0JJPhx+8pt7ij9tGDwKBJfV5aofWfw6/VlN vQ/piN/DmiSGmWMqdTMdA29EOc31N0VqD5V2GURVGkkIf5oKDSq3cHe8bZVA0D1GHk7RYFutVIS WSc613CEDsiJWhlQTcYp8gKcBMHw X-Received: by 2002:a05:6808:1a0d:b0:487:530f:5754 with SMTP id 5614622812f47-49219003ff8mr11595024b6e.20.1782689711560; Sun, 28 Jun 2026 16:35:11 -0700 (PDT) MIME-Version: 1.0 References: <19536-73ce5847e6c0e7b1@postgresql.org> In-Reply-To: From: Bharath Rupireddy Date: Sun, 28 Jun 2026 16:34:59 -0700 X-Gm-Features: AVVi8CfEZZaiGOHzrBdW-Xxagl3LGR83z-f8PW6e2OQYbNzFarYDXBMXSbdXPi8 Message-ID: Subject: Re: BUG #19536: UPDATE RETURNING OLD value is stale after concurrent update when table has a BEFORE UPDATE trigger To: Dean Rasheed Cc: bobergj@gmail.com, pgsql-bugs@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi, On Sun, Jun 28, 2026 at 2:59=E2=80=AFAM Dean Rasheed wrote: > > On Sat, 27 Jun 2026 at 20:22, Bharath Rupireddy > wrote: > > > > In short: with a BEFORE UPDATE trigger, the trigger's tuple lock > > advances tupleid to the concurrently-updated row version, so the later > > table_tuple_update returns TM_Ok instead of TM_Updated. Since oldSlot > > is only refreshed on the TM_Updated path, the RETURNING clause keeps > > the pre-wait value (ctid=3D(0,1), n=3D7) instead of the actual > > concurrently-updated value (ctid=3D(0,2), n=3D17). Without the trigger, > > the wait happens inside table_tuple_update itself, which returns > > TM_Updated and correctly refreshes oldSlot. > > Yes, that analysis seems correct. Thanks for the patch! This matches the fix I had in mind after my analysis as well. A couple of comments: 1/ I think the refetch (which is only needed when RETURNING references the OLD tuple) seems unnecessary. Fetching the row version comes with an additional buffer pool lookup (possibly a cache miss), a buffer pin, etc. Could we instead reuse the tuple that GetTupleForTrigger has already fetched? I might be overthinking the performance overhead here, but on busy production systems we can't ignore these costs. Others may have a different take. 2/ + context->tmfd.traversed =3D false; if (!ExecUpdatePrologue(context, resultRelInfo, tupleid, oldtuple, slot, NULL)) return NULL; Setting traversed to false here seems a bit off. Is this needed because the caller doesn't initialize ModifyTableContext properly? 3/ > This doesn't affect DELETE, because the DELETE code always fetches the > most recent version of the old tuple just before processing the > RETURNING clause. > > Similarly, it doesn't affect a cross-partition > UPDATE, which does a DELETE followed by an INSERT. > > It also doesn't affect MERGE UPDATE/DELETE, because that does its own > EPQ handling, rather than relying on the trigger code to do it (see > 9321c79c86e). I haven't checked these paths in depth, but I think it's worth adding tests for all of them. 4/ + if (context->tmfd.traversed) + { + if (!table_tuple_fetch_row_version(resultRelInfo->ri_RelationDesc, + tupleid, + SnapshotAny, + oldSlot)) + elog(ERROR, "failed to fetch tuple being updated"); + } Could we add some context to the error message, e.g., mentioning that this happens after processing the BEFORE UPDATE row triggers? That would make it easier to distinguish from the other similar ones. -- Bharath Rupireddy Amazon Web Services: https://aws.amazon.com