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.94.2) (envelope-from ) id 1tH4ar-0036DX-0w for pgsql-general@arkaria.postgresql.org; Fri, 29 Nov 2024 17:15:49 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1tH4ao-009C6U-02 for pgsql-general@arkaria.postgresql.org; Fri, 29 Nov 2024 17:15:47 +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.94.2) (envelope-from ) id 1tH4am-009C6M-RP for pgsql-general@lists.postgresql.org; Fri, 29 Nov 2024 17:15:46 +0000 Received: from fout-a7-smtp.messagingengine.com ([103.168.172.150]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1tH4ak-000C6w-OU for pgsql-general@lists.postgresql.org; Fri, 29 Nov 2024 17:15:44 +0000 Received: from phl-compute-09.internal (phl-compute-09.phl.internal [10.202.2.49]) by mailfout.phl.internal (Postfix) with ESMTP id 2B4D91380459; Fri, 29 Nov 2024 12:15:40 -0500 (EST) Received: from phl-mailfrontend-02 ([10.202.2.163]) by phl-compute-09.internal (MEProxy); Fri, 29 Nov 2024 12:15:40 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:cc:content-transfer-encoding :content-type:content-type:date:date:feedback-id:feedback-id :from:from:in-reply-to:in-reply-to:message-id:mime-version :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender :x-me-sender:x-sasl-enc; s=fm1; t=1732900540; x=1732986940; bh=V 7OOYPZD2VIhxs8m4QiXq5N6FnFih5DC5x+OW4lxfB8=; b=OvvxUalH/u6Ms11Wm +WzZ1M1u6MZ8BYgbRhTM4UHklAm/3szkH7D/LkvRfQrRGY+eejBEuaUqug76a13o QzxRswrCO9JwrYSt9+DhZq5hApcHxqe5R0j7sZHSqY3lsqEsJxYzQB8XmPeBI+3f R3d/ZuNdDRnZ9+E5b9pR/wG3ISsYk4084JyuMe/AakPVi9sf5qQHDUokUH9gZsde 2G2GeUZjo0YIVirU34FvaoTnWUPvc39cJ7P0bVgB+YgTm2Pktd/qJTk42lKXS/5W J/si3XWGYUZo1ch7tiAyvX96TQ/DkA6XoCQVEtbZC8oFkloUzvbjXDLKVJucdFH/ nb97Q== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefuddrheefgdeliecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdpuffr tefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnth hsucdlqddutddtmdenucfjughrpeffhffvvefukfggtggugfgjsehtkeertddttdejnecu hfhrohhmpeetlhhvrghrohcujfgvrhhrvghrrgcuoegrlhhvhhgvrhhrvgesrghlvhhhrd hnohdqihhprdhorhhgqeenucggtffrrghtthgvrhhnpedvkedtffduffdtffffheffhfej jefhgfeiueeukeejkeffgfdufffhudffffeuveenucffohhmrghinhepvghnthgvrhhprh hishgvuggsrdgtohhmnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghi lhhfrhhomheprghlvhhhvghrrhgvsegrlhhvhhdrnhhoqdhiphdrohhrghdpnhgspghrtg hpthhtohepgedpmhhouggvpehsmhhtphhouhhtpdhrtghpthhtoheprggurhhirghnrdhk lhgrvhgvrhesrghklhgrvhgvrhdrtghomhdprhgtphhtthhopehprghulhdrfhhovghrsh htvghrsehgmhgrihhlrdgtohhmpdhrtghpthhtohepphhgshhqlhdqghgvnhgvrhgrlhes lhhishhtshdrphhoshhtghhrvghsqhhlrdhorhhgpdhrtghpthhtohepthhglhesshhssh drphhghhdrphgrrdhush X-ME-Proxy: Feedback-ID: ia2694551:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Fri, 29 Nov 2024 12:15:38 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=alvh.no-ip.org; s=schmee; t=1732900535; bh=zSIBbF0zy4i4UFSlJz87v1T6aeBL7vjf5qad4855eek=; h=Date:From:To:Cc:Subject:In-Reply-To:From; b=PtUEwReMuSL6qjRjWbLBPJI7QDQRAL0kAB6RRSav0MpUquVI/GKOw9n2AI2Y14PyQ jEsCb91PZr3d8xy1HHLg6CoiHoc3nYs9lxnm2K3xxrDumK8Mq/RpQSxS7lcdujRjdv uNtkSgFTVohoipyLfh5PNKargPdmLVkscdIPw2H7Z1KGKP2fL5XVWwZ/zT70PreGeu XcCTPOvlSK4KoHAKqwXi5Y+RzSVeFURA5w7T56FBNOBGnsZOrkXSDSntcx0J/ocuIV Uk5KDoyncZ3H/L1uI+qdPA+aESyCtzc5UKA1aWM8HMPdILIaJFL1n6RgRqIFqNPSkF bvHnTy1AeBP4g== Received: by schmee.alvh.no-ip.org (Postfix, from userid 1000) id 9E7B395; Fri, 29 Nov 2024 18:15:35 +0100 (CET) Date: Fri, 29 Nov 2024 18:15:35 +0100 From: Alvaro Herrera To: Tom Lane Cc: Paul Foerster , Adrian Klaver , pgsql-general list Subject: Re: PostgreSQL 15.10 update corrective action for ATTACH PARTITION/DETACH PARTITION Message-ID: <202411291715.lwona3kp2xvv@alvherre.pgsql> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <1900196.1732733556@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On 2024-Nov-27, Tom Lane wrote: > I doubt that there's anything actually wrong with the catalog state at > this point (perhaps Alvaro would confirm that). That leads to the > conclusion that what's wrong is the release notes' query for fingering > broken constraints, and it needs some additional test to avoid > complaining about (I suspect) self-reference cases. Yes, I think the catalog state is correct and the release notes query is wrong. I propose a repaired version below. But first, I think there's still a problem specific to partition creation when a self-referencing FKs exists. If you do create table / create partition / add FK, then the query from the release notes does report the FK. But if you do create table / add FK / create partition, nothing is reported. Clearly, both those things cannot be simultaneously correct. -- Case 1: create the partition when the FK already exists drop table if exists p_ci_pipelines; create table p_ci_pipelines(partition_id int, id int, primary key(partition_id,id), auto_canceled_by_partition_id int, auto_canceled_by_id int) partition by LIST (partition_id); ALTER TABLE p_ci_pipelines ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines (partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL; create table ci_pipelines partition of p_ci_pipelines FOR VALUES IN ('100', '101', '102'); -- Case 2: create both tables, then add the FK drop table if exists p_ci_pipelines; create table p_ci_pipelines(partition_id int, id int, primary key(partition_id,id), auto_canceled_by_partition_id int, auto_canceled_by_id int) partition by LIST (partition_id); create table ci_pipelines partition of p_ci_pipelines FOR VALUES IN ('100', '101', '102'); ALTER TABLE p_ci_pipelines ADD CONSTRAINT fk_262d4c2d19_p FOREIGN KEY (auto_canceled_by_partition_id, auto_canceled_by_id) REFERENCES p_ci_pipelines (partition_id, id) ON UPDATE CASCADE ON DELETE SET NULL; Naturally, if in any of those situations you drop and recreate the FK, it degenerates to case 2, so if you do what the release notes say, it'll continue to report the FK. We can use the following query (which lists the constraint and its derivate pg_constraint rows) to see what goes wrong: WITH RECURSIVE arrh AS ( SELECT oid, conrelid, conname, confrelid, NULL::name AS conparent FROM pg_constraint WHERE connamespace = 'public'::regnamespace AND contype = 'f' AND conparentid = 0 UNION ALL SELECT c.oid, c.conrelid, c.conname, c.confrelid, (pg_identify_object('pg_constraint'::regclass, arrh.oid, 0)).identity FROM pg_constraint c JOIN arrh ON c.conparentid = arrh.oid ) SELECT conrelid::regclass, conname, confrelid::regclass, conparent FROM arrh ORDER BY conrelid::regclass::text, conname; For case 2, this is the result: conrelid │ conname │ confrelid │ conparent ────────────────┼─────────────────────────────────────────────────────────────────┼────────────────┼────────────────────────────────────────── ci_pipelines │ fk_262d4c2d19_p │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines p_ci_pipelines │ fk_262d4c2d19_p │ p_ci_pipelines │ p_ci_pipelines │ p_ci_pipelines_auto_canceled_by_partition_id_auto_canceled_fkey │ ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines For case 1, where the release notes query reports nothing, we get the following list of constraints instead: conrelid │ conname │ confrelid │ conparent ────────────────┼─────────────────┼────────────────┼────────────────────────────────────────── ci_pipelines │ fk_262d4c2d19_p │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines p_ci_pipelines │ fk_262d4c2d19_p │ p_ci_pipelines │ (2 filas) Let's look at the triggers. For case 1 we have the following triggers: WITH RECURSIVE arrh AS ( SELECT t.oid, t.tgrelid::regclass as tablename, tgname, t.tgfoid::regproc as trigfn, (pg_identify_object('pg_constraint'::regclass, c.oid, 0)).identity as constr, NULL::bool as samefunc, NULL::name AS parent FROM pg_trigger t LEFT JOIN pg_constraint c ON c.oid = t.tgconstraint WHERE (SELECT relnamespace FROM pg_class WHERE oid = t.tgrelid) = 'public'::regnamespace AND c.contype = 'f' AND t.tgparentid = 0 UNION ALL SELECT t2.oid, t2.tgrelid::regclass as tablename, t2.tgname, t2.tgfoid::regproc as trigfn, (pg_identify_object('pg_constraint'::regclass, c2.oid, 0)).identity, arrh.trigfn = t2.tgfoid as samefunc, replace((pg_identify_object('pg_trigger'::regclass, t2.tgparentid, 0)).identity, t2.tgparentid::text, 'TGOID') FROM pg_trigger t2 LEFT JOIN pg_constraint c2 ON c2.oid = t2.tgconstraint JOIN arrh ON t2.tgparentid = arrh.oid ) SELECT tgname, tablename, constr, samefunc, parent FROM arrh ORDER BY tablename::text, constr; tgname │ tablename │ constr │ samefunc │ parent ──────────────────────────────┼────────────────┼──────────────────────────────────────────┼──────────┼───────────────────────────────────────────────────────── RI_ConstraintTrigger_c_16659 │ ci_pipelines │ fk_262d4c2d19_p on public.ci_pipelines │ t │ "RI_ConstraintTrigger_c_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_c_16658 │ ci_pipelines │ fk_262d4c2d19_p on public.ci_pipelines │ t │ "RI_ConstraintTrigger_c_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_a_16648 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_a_16649 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_c_16650 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_c_16651 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ (6 filas) For case 2 we have this: tgname │ tablename │ constr │ samefunc │ parent ──────────────────────────────┼────────────────┼──────────────────────────────────────────────────────────────────────────────────────────┼──────────┼───────────────────────────────────────────────────────── RI_ConstraintTrigger_c_16680 │ ci_pipelines │ fk_262d4c2d19_p on public.ci_pipelines │ t │ "RI_ConstraintTrigger_c_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_c_16679 │ ci_pipelines │ fk_262d4c2d19_p on public.ci_pipelines │ t │ "RI_ConstraintTrigger_c_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_a_16675 │ ci_pipelines │ p_ci_pipelines_auto_canceled_by_partition_id_auto_canceled_fkey on public.p_ci_pipelines │ t │ "RI_ConstraintTrigger_a_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_a_16674 │ ci_pipelines │ p_ci_pipelines_auto_canceled_by_partition_id_auto_canceled_fkey on public.p_ci_pipelines │ t │ "RI_ConstraintTrigger_a_TGOID" on public.p_ci_pipelines RI_ConstraintTrigger_a_16671 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_a_16672 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_c_16676 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ RI_ConstraintTrigger_c_16677 │ p_ci_pipelines │ fk_262d4c2d19_p on public.p_ci_pipelines │ │ (8 filas) Here, the difference is the two action triggers on the partition, which hang under the secondary constraint for the partition. And those are critical, because without them, the ON DELETE clause is not executed: insert into ci_pipelines values (100, 1, null, null); -- create referenced row insert into ci_pipelines values (101, 2, 100, 1); -- create the reference delete from ci_pipelines where id = 1; -- should SET NULL but doesn't select * from p_ci_pipelines ; partition_id │ id │ auto_canceled_by_partition_id │ auto_canceled_by_id ──────────────┼────┼───────────────────────────────┼───────────────────── 101 │ 2 │ 100 │ 1 (1 fila) (Obviously if we drop the constraint at this point and try to recreate, it'll complain that the referenced row doesn't exist). This doesn't happen with the tables defined as case 2; the FK columns are set to NULL, as intended. partition_id │ id │ auto_canceled_by_partition_id │ auto_canceled_by_id ──────────────┼────┼───────────────────────────────┼───────────────────── 101 │ 2 │ │ (1 fila) This all was to say that the query in the release notes is undoubtedly wrong. After thinking some more about it, I think the fix is to add 1 to the number of constraints: SELECT conrelid::pg_catalog.regclass AS "constrained table", conname AS constraint, confrelid::pg_catalog.regclass AS "references", pg_catalog.format('ALTER TABLE %s DROP CONSTRAINT %I;', conrelid::pg_catalog.regclass, conname) AS "drop", pg_catalog.format('ALTER TABLE %s ADD CONSTRAINT %I %s;', conrelid::pg_catalog.regclass, conname, pg_catalog.pg_get_constraintdef(oid)) AS "add" FROM pg_catalog.pg_constraint c WHERE contype = 'f' AND conparentid = 0 AND (SELECT count(*) FROM pg_catalog.pg_constraint c2 WHERE c2.conparentid = c.oid) <> ((SELECT count(*) FROM pg_catalog.pg_inherits i WHERE (i.inhparent = c.conrelid OR i.inhparent = c.confrelid) AND EXISTS (SELECT 1 FROM pg_catalog.pg_partitioned_table WHERE partrelid = i.inhparent)) + CASE when pg_partition_root(conrelid) = confrelid THEN 1 ELSE 0 END); This reports case 2 as OK and case 1 as bogus, as should be. I tried adding more partitions and this seems to hold correctly. I was afraid though that this would fail if we create an FK in an intermediate level of the partition hierarchy ... but experimentation doesn't seem to give that result. I've run out of time today to continue to look though. -- Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/ "La vida es para el que se aventura"