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 1wYUGY-000Hth-0I for pgsql-hackers@arkaria.postgresql.org; Sat, 13 Jun 2026 19:43:38 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wYUGW-004JXe-1t for pgsql-hackers@arkaria.postgresql.org; Sat, 13 Jun 2026 19:43:36 +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 1wYUGW-004JXT-0N for pgsql-hackers@lists.postgresql.org; Sat, 13 Jun 2026 19:43:36 +0000 Received: from mail-ed1-x535.google.com ([2a00:1450:4864:20::535]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wYUGU-00000000DL2-18xx for pgsql-hackers@lists.postgresql.org; Sat, 13 Jun 2026 19:43:35 +0000 Received: by mail-ed1-x535.google.com with SMTP id 4fb4d7f45d1cf-68cebf2657cso306713a12.3 for ; Sat, 13 Jun 2026 12:43:33 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1781379811; cv=none; d=google.com; s=arc-20240605; b=IzmUzsYoYQ0Ysc1Krlq4DLNL1wR5SgmpNm1LuTIzqFJ/Cdv8ILeqJz69VqbpG7f2PE VWGEZRdacj78mmLSMcLyww5Oui+6qKQyGIeqR3KUx1M8JVypO4xQMikuQJ1cKy9qM4wA c+dVkD7+n9kT2wyBiYo4cqAXZdE4Ni7U6BUfE70WtUHvi6xKOqT7qVujcymLix0j6Ibt RiPTqJR3FfFxVc7hOGF2Pbkwp8s9njW7e+lg0KHsPlFiNYS4ZI5hwHuinYLjQ+hqqR+J eJZcl1RG//5HdaofNOWoXffGeCVIM1jypGh0GtsUIiAvg+OEpPVUYPlDRkMbyjP00BM0 Logw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=iZPXCrT/4YmfcLKzwLCgF+6ftNDSk3sS+JcTkJUEs/c=; fh=qxcrYdMs21rtrxHU4/6c5jc0GWfND4BWfUoTtb+ouxg=; b=XRN+PJtS+EcU1Z9diK6OB+tq1itzCb9ovvD6l4MyRUYwUbgnYxtrOMYxdRrpuhEk8v VzBJbUxKxkgopnqCcjLq0qbAmGIbDWnEhUYQjvf5d3C4WAqDiqBGneIXbcUHD2hcEreG Pus7M5V2Lz0ty+re8nWan0rfFbOj6kW7WY1Sq1JHw8pW18lALW+HQiiMiknctXFZbf1O 4B83htwOM103rc3LqhWMKhQ09wHLPvcRPcVLfveTCCen8pwb9Vd5VPasSp1rAyiRVOwv qLP+zZ+eAEps9faZD5Nrta34GiNySIga1prqB1unyVqBCNMlZHNLi37+7/DLm8jRWr+w 4ZzQ==; 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=1781379811; x=1781984611; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=iZPXCrT/4YmfcLKzwLCgF+6ftNDSk3sS+JcTkJUEs/c=; b=WAmKktD65+B3Mjo50hBRXmkIWUZnjLHGrLDxHgKRRl4kfmQ8ugnQYAJCpY74OVz/Wc DNd7e87ghYop/t/oz4Th7VoRQXewerACSxvLcq/BVywnoYiiQzerhoY3JapFNjKkkAha zZKJsfMu1e73NhHYn4E5thXmzcs04QEkcy1kHfasG3bi8oz1TTpeWNbhNzawSL/3/b+v c1539imumqjMJAZuTZhpx6X0IV+Y67GbbklYI5QyDCIluwmqwFl4F3amv2ch4wYvSmcD EYOTza15pcF7hhO3FJLpqdh5Y9bJIE/07h0HQwXcGlSBw+YpnJJUA9RxBhE69K1rjrZY h/XQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1781379811; x=1781984611; h=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=iZPXCrT/4YmfcLKzwLCgF+6ftNDSk3sS+JcTkJUEs/c=; b=Y7QihCOl4UDgVYVz87FXVj5cqhCDvDpkYwh+W5bT5wq9TO2ImYMBbWvR18GqF1YcfY ob6rXrQktu7h9PvoVI+Lstt7EsZN/OE+M26hlbgZJFYI4f4bdP0M0E+xV59zB9Lxjj1l aakAj3Lc66IN26TJhaknDjfBFj991U8/FrX2c+utADfsQ7Q1rvYKDcDVaadXL0/RuhkV GSOpqwTx1SiS0xafjN4Bq7PNpUBueW+mYp+gs6FTCk1UTYIrFpDxcyLbWG3Gfqdm0kd6 f3/B8253SN7yVIYLB4FA6cbBZR3RC2O3PWf6/0gW6DlAhJGTDFTZH2rFGK/vG5QLtNsH NFGg== X-Forwarded-Encrypted: i=1; AFNElJ/ttEeKH9HcIB86Jun+J2CvWIT7ZyR6VwIQE/HWbryuiFh9qS2VrSUXHWyrnYlsnMtUlBbcL7DD4vpXow3t@lists.postgresql.org X-Gm-Message-State: AOJu0YziuJXj8gD5bvuAWmoHwGVbtRqco5Plr5+ro5zJ0jrTwovQZ7Gx rX1HpxktiZmzy76up5Q+j1BAJiE6VR2koKvjHrGUKv/E8XM0k8wk1BT5xwh/70HIQmMb7xokdzw WLEQxBs2hBebgbKghZB6q1rQliwLAwG0= X-Gm-Gg: Acq92OEsswMwjJk9yskijW8p7nhuhPTL2FAq2FGFSREqWjUIeewdrCHdbEWrIOXCzSQ FcsnQJJUPBDZIwsT3VmYsqMpq9CnvF+qx9YXRsC1SG5ZwGnYaELUe2vc/sSz4+u5LYTy3ql2BAl tcSo0Uu2PjFXzvV9uUYmi/owO045we+d1WVBUg7tfqmw6PUk98L84/mmhCluyyR7jmJaDLI82rQ PxeRBHouy3gvt8NZxr7t2TxBlqzuKyXIQOB7zrBK42JAYPlhFileQYAy5zjZJbA7yl1TQbWW2tU 5mJp X-Received: by 2002:a05:6402:3810:b0:660:ce16:e5ce with SMTP id 4fb4d7f45d1cf-693784fd887mr1262486a12.2.1781379809694; Sat, 13 Jun 2026 12:43:29 -0700 (PDT) MIME-Version: 1.0 References: <20250718175314.4513c00a@karst> <20250729174852.14f23557@karst> In-Reply-To: From: Nikita Malakhov Date: Sat, 13 Jun 2026 22:43:17 +0300 X-Gm-Features: AVVi8CeNq5rIcoV9_76fri7OwqY41A1cEhM6ev-nJRzT_136LeO1WjOCqzPTGoU Message-ID: Subject: Re: [(known) BUG] DELETE/UPDATE more than one row in partitioned foreign table To: Michael Paquier Cc: Etsuro Fujita , Jehan-Guillaume de Rorthais , pgsql-hackers@lists.postgresql.org Content-Type: multipart/alternative; boundary="000000000000c45ad6065427ce4c" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000c45ad6065427ce4c Content-Type: text/plain; charset="UTF-8" Hi! While testing the proposed solution we've stumbled upon another vanilla bug related to FDW - a query with DELETE ... USING selects invalid records from partitioned FDW tables: CREATE EXTENSION postgres_fdw; CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw; DO $d$ BEGIN EXECUTE $$CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname '$$||current_database()||$$', port '$$||current_setting('port')||$$' )$$; END; $d$; CREATE USER MAPPING FOR public SERVER testserver1 OPTIONS (user 'value', password 'value'); CREATE USER MAPPING FOR CURRENT_USER SERVER loopback; CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2; CREATE USER MAPPING FOR public SERVER loopback3; CREATE TABLE acc_entry ( id bigint, doc_date date, impact int, amount numeric ) PARTITION BY RANGE (doc_date); CREATE TABLE acc_entry_p1 PARTITION OF acc_entry FOR VALUES FROM ('2025-01-01') TO ('2025-07-01'); CREATE TABLE acc_entry_p2 PARTITION OF acc_entry FOR VALUES FROM ('2025-07-01') TO ('2026-01-01'); CREATE FOREIGN TABLE measurement_fdw ( id bigint, doc_date date, impact int, amount numeric ) SERVER loopback OPTIONS (table_name 'acc_entry'); INSERT INTO acc_entry SELECT CASE WHEN g IN (4,15,26,35,46,55,66,75,86,95) THEN 2501020100000124 ELSE g END AS id, CASE WHEN g % 2 = 0 THEN timestamp '2025-02-02' ELSE timestamp '2025-08-08' END, 1, g FROM generate_series(1,100) g; DELETE FROM measurement_fdw USING ( SELECT id FROM measurement_fdw WHERE id = 2501020100000124 LIMIT 1 ) s WHERE measurement_fdw.id = s.id; The latter query selects and deletes records with invalid ID which should not be selected at all. Although rewritten query like with sub as ( select t1.id sub_id from measurement_fdw t1 where t1.id=2501020100000124 limit 1 ) select m.id, m.doc_date, m.impact, m.amount from measurement_fdw m, sub where m.id = sub.sub_id; works correctly. Currently I try to figure out what's the cause of this strange behavior and I'm suspicious about /* * WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on * the existing target row. */ add_with_check_options(rel, rt_index, WCO_RLS_MERGE_DELETE_CHECK, merge_delete_permissive_policies, merge_delete_restrictive_policies, withCheckOptions, hasSubLinks, hasSubLinks, true); -- Regards, Nikita Malakhov Postgres Professional The Russian Postgres Company https://postgrespro.ru/ --000000000000c45ad6065427ce4c Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi!

While testing the = proposed solution we've stumbled upon another vanilla bug related to FD= W -
a query with DELETE ... USING selects invalid records from pa= rtitioned FDW tables:

CREATE EXTENSION postgres_fd= w;

CREATE SERVER testserver1 FOREIGN DATA WRAPPER postgres_fdw;
D= O $d$
=C2=A0 =C2=A0 BEGIN
=C2=A0 =C2=A0 =C2=A0 =C2=A0 EXECUTE $$CREAT= E SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
=C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 OPTIONS (dbname '$$||current_database()||$$',=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0port '$$||current_setting('port')||$$'
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 )$$;
=C2=A0 =C2=A0 END;
$d$;

C= REATE USER MAPPING FOR public SERVER testserver1
OPTIONS (user 'val= ue', password 'value');
CREATE USER MAPPING FOR CURRENT_USER= SERVER loopback;
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback2;=
CREATE USER MAPPING FOR public SERVER loopback3;

CREATE TABLE ac= c_entry
(
=C2=A0 =C2=A0 id bigint,
=C2=A0 =C2=A0 doc_date date,=C2=A0 =C2=A0 impact int,
=C2=A0 =C2=A0 amount numeric
) PARTITION B= Y RANGE (doc_date);

CREATE TABLE acc_entry_p1
PARTITION OF acc_en= try
FOR VALUES FROM ('2025-01-01') TO ('2025-07-01');
CREATE TABLE acc_entry_p2
PARTITION OF acc_entry
FOR VALUES FROM= ('2025-07-01') TO ('2026-01-01');

CREATE FOREIGN TA= BLE measurement_fdw
(
=C2=A0 =C2=A0 id bigint,
=C2=A0 =C2=A0 doc_d= ate date,
=C2=A0 =C2=A0 impact int,
=C2=A0 =C2=A0 amount numeric
)=
SERVER loopback
OPTIONS (table_name 'acc_entry');

INS= ERT INTO acc_entry
SELECT
=C2=A0 =C2=A0 CASE
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 WHEN g IN (4,15,26,35,46,55,66,75,86,95)
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 THEN 2501020100000124
=C2=A0 =C2=A0 =C2=A0 =C2=A0 ELSE= g
=C2=A0 =C2=A0 END AS id,
=C2=A0 =C2=A0 CASE WHEN g % 2 =3D 0 THEN = timestamp '2025-02-02' ELSE timestamp '2025-08-08' END,
= =C2=A0 =C2=A0 1,
=C2=A0 =C2=A0 g
FROM generate_series(1,100) g;
DELETE FROM measurement_fdw
USING (
=C2=A0 =C2=A0 SELECT id
=C2= =A0 =C2=A0 FROM measurement_fdw
=C2=A0 =C2=A0 WHERE id =3D 2501020100000= 124
=C2=A0 =C2=A0 LIMIT 1
) s
WHERE measurement_fdw.id =3D s.id;

The latter query selects and deletes records with in= valid ID which should not be selected at all.
Although rewritten = query like
with sub as (
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0select t1.id sub_id
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0from measurement_fdw t1
=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0where t1.id=3D250102010000012= 4
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0limit 1
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 )
select m.id, m.doc_date, m.impa= ct, m.amount from measurement_fdw m, sub
=C2=A0 =C2=A0 =C2=A0 =C2=A0 whe= re m.id =3D sub.sub_id;
works correct= ly.
Currently I try to figure out what's the cause of this st= range behavior and I'm suspicious about
/*
=C2=A0* = WCO_RLS_MERGE_DELETE_CHECK is used to check DELETE USING quals on
=C2=A0= * the existing target row.
*/
add_with_check_options(rel, rt_index,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 WCO= _RLS_MERGE_DELETE_CHECK,
merge_delete_permissive_policies,
merge_dele= te_restrictive_policies,
withCheckOptions,
hasSubLinks,
hasSubLink= s,
true);

--
Regards,
Nikita Malakhov
Postgres Professional
The Russian Postgres Company
--000000000000c45ad6065427ce4c--