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 1vVwP8-009vph-0s for pgsql-general@arkaria.postgresql.org; Wed, 17 Dec 2025 18:37:43 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vVwP7-00Fkov-0I for pgsql-general@arkaria.postgresql.org; Wed, 17 Dec 2025 18:37:41 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVwP6-00Fkon-2U for pgsql-general@lists.postgresql.org; Wed, 17 Dec 2025 18:37:41 +0000 Received: from sss.pgh.pa.us ([68.162.161.243]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vVwP4-001HFp-32 for pgsql-general@postgresql.org; Wed, 17 Dec 2025 18:37:41 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 5BHIbYNw2654836; Wed, 17 Dec 2025 13:37:34 -0500 From: Tom Lane To: Joe Conway cc: Matthias Leisi , pgsql-general Subject: Re: Record last SELECT on a row? In-reply-to: <91687275-3826-49fc-b705-70ab2b6e0bcf@joeconway.com> References: <287E4DF6-35A2-4062-AEBA-32DB1DE35C5D@leisi.net> <91687275-3826-49fc-b705-70ab2b6e0bcf@joeconway.com> Comments: In-reply-to Joe Conway message dated "Wed, 17 Dec 2025 11:46:14 -0500" MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <2654834.1765996654.1@sss.pgh.pa.us> Date: Wed, 17 Dec 2025 13:37:34 -0500 Message-ID: <2654835.1765996654@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Joe Conway writes: > Possibly try using/abusing RLS? Cute idea, but I think it doesn't reliably address the problem of wanting to identify the specific rows that were read. In your toy example it'd work, because the generated plan is regression=> explain verbose select * from t1 where c1=42; QUERY PLAN ------------------------------------------------------------ Seq Scan on public.t1 (cost=0.00..343.38 rows=2 width=36) Output: c1, c2 Filter: ((t1.c1 = 42) AND audit(t1.c1)) (3 rows) so the WHERE clause is applied before the RLS filter. But in any slightly-more-complicated situation, like a non-leakproof WHERE condition, the order would be reversed so the log would overstate which rows were read. If the application's behavior is simple and well-defined, this might be good enough, of course. I thought of a way that could possibly do this reliably, but it's vastly more work than the use-case seems worth: 1. Convert the SELECTs into SELECT FOR UPDATE (you could do this without changing the application, by interposing a view). SELECT FOR SHARE might be good enough, not sure. 2. Write a logical replication output plugin that parses the WAL log well enough to identify the tuple locks taken by FOR UPDATE. This should work to log only the rows actually read, because FOR UPDATE is postponed to the top of the query plan, unlike RLS. regards, tom lane