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 1vkuo4-004FUH-1a for pgsql-hackers@arkaria.postgresql.org; Wed, 28 Jan 2026 01:57:21 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vkuo1-00GZNP-1j for pgsql-hackers@arkaria.postgresql.org; Wed, 28 Jan 2026 01:57:17 +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 1vkuo1-00GZNG-0S for pgsql-hackers@lists.postgresql.org; Wed, 28 Jan 2026 01:57:17 +0000 Received: from mail-dl1-x1236.google.com ([2607:f8b0:4864:20::1236]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1vkuny-00000000nne-3aHr for pgsql-hackers@lists.postgresql.org; Wed, 28 Jan 2026 01:57:17 +0000 Received: by mail-dl1-x1236.google.com with SMTP id a92af1059eb24-11f36012fb2so9071594c88.1 for ; Tue, 27 Jan 2026 17:57:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1769565431; x=1770170231; darn=lists.postgresql.org; h=message-id:in-reply-to:to:references:date:subject:mime-version :content-transfer-encoding:from:from:to:cc:subject:date:message-id :reply-to; bh=zTDouyXQlFzs0kbYyPq9oAQ2/KQa5TL8I5Dgld8a5yM=; b=MJItPot2tXEyf5Cdfi9T6wjbOXUn1o24zXm7WQbUjoKvQ08Tsn2InkA+a9AT3ktoc6 nvQbOL6sSaP5iimlSZpwtzRVQ15vMFl2YdANKPMaGOvYzHnhxzZHX95dwVr/6lxFqPrQ RXQKSB7Og2aXz6iIeAdxE2e/csNtgJN9RXRkEAOnP8Hk/qJ4eYpWyFI2HEInOG2FvdnC gdQaPTNT/GK+qQGdKIuofcF+kHRfJfrTZgKGwV6Wp14l5jI/T1+NBJd9O9/yUGn7lVtc 0osXti63EDohpsYWLxxxXOVk/2UX1EIAPwg/xKA7a9zuJnwkN2LOKqeX7n29UjDyHBc2 I4uw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769565431; x=1770170231; h=message-id:in-reply-to:to:references:date:subject:mime-version :content-transfer-encoding:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=zTDouyXQlFzs0kbYyPq9oAQ2/KQa5TL8I5Dgld8a5yM=; b=KTEgD5DSRoCKZNlnlwyzTyKGTfudyJP95rfcWGQKSTMdSaXBXhYavEIhMBhNvcgpc+ 1GyFDxUwBdKaImFrlF2kGT0zCXyroI+pmk8d7JLDbPr6CtuW9wsR97eTuKKxYpusX1U6 MEb1BodxPOB7eR0qvfmm5tZY11GAji6KGqyWN6lo5ZrKIYPsH7gJ7kppDZsFNBJ/GpAj Z0NcU/k/+RJw/Ik2SsyqSBBeb9EZBYnkdRFCslsR+tiebwN9vzcGovwHnTKhByoGGXRM WSpDvwIKCTld/fiBbj7MTDxCr7TNUaLM+3AGSnm4j6EdYlNYSS8a/8UrDPQ3Zwis0705 6W+g== X-Forwarded-Encrypted: i=1; AJvYcCWED8XUWvtrwGPFBfWakyHnu1iAfPcDKLctqaepbVy/nLzsd1GLXpoTo29qklmv3kHxievbes63BJ39P3Oy@lists.postgresql.org X-Gm-Message-State: AOJu0YydmL2Gcf8SdLn5LRbdOXePdQjb7c1SGLsjes/x0cvuqvwGJlU9 J+CBS+Nmlx2qbStSs8Dtwuc30bUHst92W+BZaYLVzBiuOZeco0Okc+no X-Gm-Gg: AZuq6aK/Dbdc158Dz0FSll3L8byVFwaGMDsFnzdUKJ8xfWjx8XcV8ZiSJhcAX/24Vt1 RP9Bm025Eo6Zdo/wCrUwaTpw3QjKm8iQpZpw0qDWUSiQEiA6v9Ewf5FEaGCUAZRFu841ZSvth2G Lxx56az2W61a6sCmyb5/BfEPSkIQkmvq8fHE5845dU4c9qECLQexgQrYZrfgH7rOcmR0UIvQ0dJ iO+K67yXSIG6CIolBPQ2ucNEG+7YDoBCImzjt2qdNb8dOrM82CKgriZH3/emniiEGxaB4Xq4Dvp L7hN38OsKohfUQ6fEup+TtgPsUR0CurizjXl946vXVbLeRX3ibsjc7c/Lh1465yKSHW9FqoZs1w 5liWjyPHVtH5OHfeOl3v+r7V6Urt7nk6nz4Fv2jZ3j2oAtiF16AkhZN4TXvNPCEYiLvYQDtlMUc bFrMMQ2/ISNERwlcK1QTFB X-Received: by 2002:a05:7022:68b:b0:11b:c1fb:89a with SMTP id a92af1059eb24-124a00bb09emr2617256c88.32.1769565431120; Tue, 27 Jan 2026 17:57:11 -0800 (PST) Received: from smtpclient.apple ([142.171.105.12]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-124a9e06d14sm781240c88.15.2026.01.27.17.57.09 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Tue, 27 Jan 2026 17:57:10 -0800 (PST) From: Chao Li Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3864.300.41.1.7\)) Subject: Re: tablecmds: reject CLUSTER ON for partitioned tables earlier Date: Wed, 28 Jan 2026 09:56:36 +0800 References: <07773235-2E94-478F-BEF6-38C73B0553B8@gmail.com> <16D5D52A-1B99-4371-982E-257C195D2924@gmail.com> <5244008D-79E1-484B-9407-21F5D388EC7F@gmail.com> To: Michael Paquier , Zsolt Parragi , PostgreSQL Hackers In-Reply-To: Message-Id: X-Mailer: Apple Mail (2.3864.300.41.1.7) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk > On Jan 27, 2026, at 15:48, Michael Paquier = wrote: >=20 > On Tue, Jan 27, 2026 at 07:13:04AM +0800, Chao Li wrote: >> I added two new test cases in 0002 that trigger the check. >>=20 >> BTW, this is the CF entry: >> https://commitfest.postgresql.org/patch/6415/. You may mark yourself >> as a reviewer, and once you consider the patch is ready to go, would >> you mind change the status to Ready For Committer? >=20 > There is more to this set of changes than it looks at first sight. >=20 > Hence, one question about 0001: can the previous error path in > mark_index_clustered() be reached through a different mean than ALTER > TABLE? If yes, we should have a test for it. If no, it could be > switched to an elog(ERROR) or an assertion. The code paths leading to > the previous error should be analyzed further. Okay, I spent time today investigating mark_index_clustered() today. First, I reset the source tree to 05fb5d6 where the partitioned table = check was added to mark_index_clustered(). The commit subject is "Ignore = partitioned indexes where appropriate=E2=80=9D. It added the check in 3 = functions: * cluster() * mark_index_clustered() * get_relation_info() - not in scope of this discussion At this commit, ALTER TABLE =E2=80=A6 CLUSTER ON / SET WITHOUT CLUSTER = code patch could reach mark_index_clustered(). Other than that, = mark_index_clustered() was only called by rebuild_relation() when the = parameter indexOid is valid; rebuild_relation() was only called by = cluster_rel(); and cluster_rel() was called by vacuum_rel() and = cluster().=20 * For the cluster() code patch: because of the check added to cluster() = by this commit, partitioned table would return early, thus = mark_index_clustered() was actually not called. * For the vacuum_rel() code path: there was already a check for = partitioned table to return early, thus cluster_rel() won=E2=80=99t be = called against partitioned tables, so that mark_index_clustered() could = not be called either. So, looks like the check added to mark_index_clustered() was only for = the ALTER TABLE code path. Then, switching the source tree back to this patch. Now, for the ALTER = TABLE code path, 0001 ensures partitioned table won=E2=80=99t reach = mark_index_clustered(). Other than ALTER TABLE, mark_index_clustered() is only called by = rebuild_relation(); rebuild_relation() is only called by cluster_rel(); = cluster_rel() is called by vacuum_rel() and cluster(). So, the call = paths are the same as commit 05fb5d6. For the cluster() code path, I traced this scenario: ``` evantest=3D# create table p (id int) partition by range (id); CREATE TABLE evantest=3D# create table p1 partition of p for values from (0) to (10); CREATE TABLE evantest=3D# create index p_idx on p (id); CREATE INDEX evantest=3D# cluster p using p_idx; CLUSTER ``` The code ran into cluster(). Now, cluster() is much complicated than it = was in commit 05fb5d6. For a partitioned table, it iterates all leaf = partitions to call cluster_rel(): ``` /* * Either we're processing a partitioned table, or we were not = given any * table name at all. In either case, obtain a list of = relations to * process. * * In the former case, an index name must have been given, so we = don't * need to recheck its "indisclustered" bit, but we have to = check that it * is an index that we can cluster on. In the latter case, we = set the * option bit to have indisclustered verified. * * Rechecking the relation itself is necessary here in all = cases. */ params.options |=3D CLUOPT_RECHECK; if (rel !=3D NULL) { Assert(rel->rd_rel->relkind =3D=3D = RELKIND_PARTITIONED_TABLE); check_index_is_clusterable(rel, indexOid, = AccessShareLock); rtcs =3D = get_tables_to_cluster_partitioned(cluster_context, indexOid); /* close relation, releasing lock on parent table */ table_close(rel, AccessExclusiveLock); } =E2=80=A6. cluster_multiple_rels(rtcs, ¶ms); // cluster_rel() is called = here ``` So, partitioned table should never reach mark_index_clustered() from the = cluster() code patch. For the vacuum_rel() code patch, same as before, partitioned table will = return early, cluster_rel() won=E2=80=99t be called at all: ``` /* * Silently ignore partitioned tables as there is no work to be = done. The * useful work is on their child partitions, which have been = queued up for * us separately. */ if (rel->rd_rel->relkind =3D=3D RELKIND_PARTITIONED_TABLE) { relation_close(rel, lmode); PopActiveSnapshot(); CommitTransactionCommand(); /* It's OK to proceed with ANALYZE on this table */ return true; } ``` In summary, with 0001, the partitioned table check in = mark_index_clustered() is no longer needed. But as = mark_index_clustered() is an extern-ed function, it might have future = callers, I think we can change ereport(ERROR) to an Assert(). I will = include the change in next revision. Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/