Thread

Commits

  1. Refactor some code around ALTER TABLE [NO] INHERIT

  2. Add regression test for ALTER TABLE .. NO INHERIT on typed tables

  3. Reject ALTER TABLE .. CLUSTER earlier for partitioned tables

  4. Ignore partitioned indexes where appropriate

  1. tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-21T03:55:27Z

    Hi Hacker,
    
    I noticed this while working other patches related to “ALTER TABLE”.
    
    “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for
    partitioned tables, but currently ATPrepCmd() allows them through and they
    only fail later at execution time.
    
    This patch rejects these commands earlier by using the existing
    ATSimplePermissions() infrastructure in ATPrepCmd(), matching the handling
    of other unsupported ALTER TABLE actions on partitioned tables (such as SET
    LOGGED / SET UNLOGGED). This makes the behavior more consistent and
    simplifies the code path.
    
    As a result, the error reported for partitioned tables changes:
    
    Before the patch:
    ```
    evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    ERROR:  cannot mark index clustered in partitioned table
    ```
    
    With the patch:
    ```
    evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    ERROR:  ALTER action CLUSTER ON cannot be performed on relation "p_test"
    DETAIL:  This operation is not supported for partitioned tables.
    ```
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
  2. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-22T09:01:38Z

    On Jan 21, 2026, at 11:55, Chao Li <li.evan.chao@gmail.com> wrote:
    
    Hi Hacker,
    
    I noticed this while working other patches related to “ALTER TABLE”.
    
    “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for
    partitioned tables, but currently ATPrepCmd() allows them through and they
    only fail later at execution time.
    
    This patch rejects these commands earlier by using the existing
    ATSimplePermissions() infrastructure in ATPrepCmd(), matching the handling
    of other unsupported ALTER TABLE actions on partitioned tables (such as SET
    LOGGED / SET UNLOGGED). This makes the behavior more consistent and
    simplifies the code path.
    
    As a result, the error reported for partitioned tables changes:
    
    Before the patch:
    ```
    evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    ERROR:  cannot mark index clustered in partitioned table
    ```
    
    With the patch:
    ```
    evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    ERROR:  ALTER action CLUSTER ON cannot be performed on relation "p_test"
    DETAIL:  This operation is not supported for partitioned tables.
    ```
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    <v1-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch>
    
    
    
    Applying the same change to INHERIT/NO INHeRIT in v2-0002. Other than that,
    fixing 2 more things for INHERIT/NO INHERIT:
    
    * The header comment of ATPrepAddInherit() was a copy-paste mistake, it
    described something totally unrelated.
    * NO INHERIT didn’t call ATPrepAddInherit() to check early, so it had to go
    deeper and run unnecessary checks.
    
    Basically, 0001 and 0002 do the same thing on two sub-commands. If
    accepted, they can be squashed.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
  3. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-22T23:30:55Z

    
    > On Jan 22, 2026, at 17:01, Chao Li <li.evan.chao@gmail.com> wrote:
    > 
    > 
    > 
    >> On Jan 21, 2026, at 11:55, Chao Li <li.evan.chao@gmail.com> wrote:
    >> 
    >> Hi Hacker,
    >> 
    >> I noticed this while working other patches related to “ALTER TABLE”.
    >> 
    >> “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for partitioned tables, but currently ATPrepCmd() allows them through and they only fail later at execution time.
    >> 
    >> This patch rejects these commands earlier by using the existing ATSimplePermissions() infrastructure in ATPrepCmd(), matching the handling of other unsupported ALTER TABLE actions on partitioned tables (such as SET LOGGED / SET UNLOGGED). This makes the behavior more consistent and simplifies the code path.
    >> 
    >> As a result, the error reported for partitioned tables changes:
    >> 
    >> Before the patch:
    >> ```
    >> evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    >> ERROR:  cannot mark index clustered in partitioned table
    >> ```
    >> 
    >> With the patch:
    >> ```
    >> evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    >> ERROR:  ALTER action CLUSTER ON cannot be performed on relation "p_test"
    >> DETAIL:  This operation is not supported for partitioned tables.
    >> ```
    >> 
    >> Best regards,
    >> --
    >> Chao Li (Evan)
    >> HighGo Software Co., Ltd.
    >> https://www.highgo.com/
    >> 
    >> 
    >> 
    >> 
    >> <v1-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch>
    > 
    > 
    > 
    > Applying the same change to INHERIT/NO INHeRIT in v2-0002. Other than that, fixing 2 more things for INHERIT/NO INHERIT:
    > 
    > * The header comment of ATPrepAddInherit() was a copy-paste mistake, it described something totally unrelated.
    > * NO INHERIT didn’t call ATPrepAddInherit() to check early, so it had to go deeper and run unnecessary checks.
    > 
    > Basically, 0001 and 0002 do the same thing on two sub-commands. If accepted, they can be squashed.
    > 
    > Best regards,
    > --
    > Chao Li (Evan)
    > HighGo Software Co., Ltd.
    > https://www.highgo.com/
    > 
    > <v2-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch><v2-0002-tablecmds-reject-INHERIT-NO-INHERIT-for-partition.patch>
    
    PFA v3:
    
    0001 is the same as v2. In 0002:
    
    * Restored the header comment of ATPrepAddInherit, because I realized the should belong to ATExecAddInherit.
    * Renamed ATPrepAddInherit to ATPrepChangeInherit.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  4. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-23T00:54:33Z

    
    > On Jan 23, 2026, at 07:30, Chao Li <li.evan.chao@gmail.com> wrote:
    > 
    > 
    > 
    >> On Jan 22, 2026, at 17:01, Chao Li <li.evan.chao@gmail.com> wrote:
    >> 
    >> 
    >> 
    >>> On Jan 21, 2026, at 11:55, Chao Li <li.evan.chao@gmail.com> wrote:
    >>> 
    >>> Hi Hacker,
    >>> 
    >>> I noticed this while working other patches related to “ALTER TABLE”.
    >>> 
    >>> “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for partitioned tables, but currently ATPrepCmd() allows them through and they only fail later at execution time.
    >>> 
    >>> This patch rejects these commands earlier by using the existing ATSimplePermissions() infrastructure in ATPrepCmd(), matching the handling of other unsupported ALTER TABLE actions on partitioned tables (such as SET LOGGED / SET UNLOGGED). This makes the behavior more consistent and simplifies the code path.
    >>> 
    >>> As a result, the error reported for partitioned tables changes:
    >>> 
    >>> Before the patch:
    >>> ```
    >>> evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    >>> ERROR:  cannot mark index clustered in partitioned table
    >>> ```
    >>> 
    >>> With the patch:
    >>> ```
    >>> evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    >>> ERROR:  ALTER action CLUSTER ON cannot be performed on relation "p_test"
    >>> DETAIL:  This operation is not supported for partitioned tables.
    >>> ```
    >>> 
    >>> Best regards,
    >>> --
    >>> Chao Li (Evan)
    >>> HighGo Software Co., Ltd.
    >>> https://www.highgo.com/
    >>> 
    >>> 
    >>> 
    >>> 
    >>> <v1-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch>
    >> 
    >> 
    >> 
    >> Applying the same change to INHERIT/NO INHeRIT in v2-0002. Other than that, fixing 2 more things for INHERIT/NO INHERIT:
    >> 
    >> * The header comment of ATPrepAddInherit() was a copy-paste mistake, it described something totally unrelated.
    >> * NO INHERIT didn’t call ATPrepAddInherit() to check early, so it had to go deeper and run unnecessary checks.
    >> 
    >> Basically, 0001 and 0002 do the same thing on two sub-commands. If accepted, they can be squashed.
    >> 
    >> Best regards,
    >> --
    >> Chao Li (Evan)
    >> HighGo Software Co., Ltd.
    >> https://www.highgo.com/
    >> 
    >> <v2-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch><v2-0002-tablecmds-reject-INHERIT-NO-INHERIT-for-partition.patch>
    > 
    > PFA v3:
    > 
    > 0001 is the same as v2. In 0002:
    > 
    > * Restored the header comment of ATPrepAddInherit, because I realized the should belong to ATExecAddInherit.
    > * Renamed ATPrepAddInherit to ATPrepChangeInherit.
    > 
    > Best regards,
    > --
    > Chao Li (Evan)
    > HighGo Software Co., Ltd.
    > https://www.highgo.com/
    > 
    > 
    > 
    > 
    > <v3-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch><v3-0002-tablecmds-reject-INHERIT-NO-INHERIT-for-partition.patch>
    
    Please ignore v3 that was badly generated.
    
    PFA v4:
    
    > 0001 is the same as v2. In 0002:
    > 
    > * Restored the header comment of ATPrepAddInherit, because I realized the should belong to ATExecAddInherit.
    > * Renamed ATPrepAddInherit to ATPrepChangeInherit.
    
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  5. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Zsolt Parragi <zsolt.parragi@percona.com> — 2026-01-23T07:43:07Z

    Hello!
    
    A simple patch and generally looks good, I only have a few observations.
    
    > “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for
    > partitioned tables, but currently ATPrepCmd() allows them through and they
    > only fail later at execution time.
    
    Looking at the ALTER TABLE documentation, for other options there is a
    mention like "This form is not currently supported on partitioned
    tables." / "This form is not supported for partitioned tables."
    
    I don't see this mentioned for CLUSTER or INHERIT. Maybe it would be
    better to also mention this in the documentation?
    
    Also, there seems to be no test for partitioned NO INHERIT, since the
    patch changes it, it could also add a test case to verify the
    behavior.
    
    rg "INHERIT" | grep "cannot be performed"
    src/test/regress/expected/alter_table.out:ERROR:  ALTER action INHERIT
    cannot be performed on relation "partitioned"
    
    rg "NO INHERIT" | grep "cannot be performed"
    # no result
    
    tablecmds.c:5202
      case AT_DropInherit: /* NO INHERIT */
      ATSimplePermissions(cmd->subtype, rel,
    - ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
    + ATT_TABLE | ATT_FOREIGN_TABLE);
      /* This command never recurses */
    + ATPrepChangeInherit(rel);
      /* No command-specific prep needed */
    
    That last comment seems to be a leftover, it's no longer true with this change.
    
    tablecmds.c:17289 trailing whitespace (in the empty line)
     /*
    + * ALTER TABLE INHERIT
    + *
    + * Add a parent to the child's parents. This verifies that all the columns and
    + * check constraints of the parent appear in the child and that they have the
    + * same data types and expressions.
    + *
      * Return the address of the new parent relation.
      */
    
    tablecmds.c:17860 - this check in ATExecDropInherit is now redundant,
    since we already have it in ATPrepChangeInherit
    
    > Before the patch:
    > ```
    > evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    > ERROR: cannot mark index clustered in partitioned table
    
    Can we still reach the original error in mark_index_clustered somehow?
    I don't see any examples in the test suite, or execution paths when I
    have looked at the code, and it can be confusing to see a different
    error code/message there.
    
    
    
    
  6. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-23T07:53:06Z

    
    > On Jan 23, 2026, at 15:43, Zsolt Parragi <zsolt.parragi@percona.com> wrote:
    > 
    > Hello!
    > 
    > A simple patch and generally looks good, I only have a few observations.
    > 
    >> “ALTER TABLE … CLUSTER ON” and "SET WITHOUT CLUSTER" are not supported for
    >> partitioned tables, but currently ATPrepCmd() allows them through and they
    >> only fail later at execution time.
    > 
    > Looking at the ALTER TABLE documentation, for other options there is a
    > mention like "This form is not currently supported on partitioned
    > tables." / "This form is not supported for partitioned tables."
    > 
    > I don't see this mentioned for CLUSTER or INHERIT. Maybe it would be
    > better to also mention this in the documentation?
    > 
    
    Hi Zsolt,
    
    Thank you very much for your review.
    
    I have the other patch for the documentation update, see [1], that is an overall clarification for alter table behaviors against partition tables. Actually, I just found this issue while working on that patch.
    
    I will handle rest of your comments soon.
    
    [1] https://www.postgresql.org/message-id/CAEoWx2%3DmYhCfsnHaN96Qqwq5b0GVS2YgO3zpVqPPRd_iO52wRw%40mail.gmail.com
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
    
    
    
  7. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-26T07:10:03Z

    
    > On Jan 23, 2026, at 15:43, Zsolt Parragi <zsolt.parragi@percona.com> wrote:
    > 
    > Also, there seems to be no test for partitioned NO INHERIT, since the
    > patch changes it, it could also add a test case to verify the
    > behavior.
    > 
    > rg "INHERIT" | grep "cannot be performed"
    > src/test/regress/expected/alter_table.out:ERROR:  ALTER action INHERIT
    > cannot be performed on relation "partitioned"
    > 
    > rg "NO INHERIT" | grep "cannot be performed"
    > # no result
    
    Added a test case.
    
    > 
    > tablecmds.c:5202
    >  case AT_DropInherit: /* NO INHERIT */
    >  ATSimplePermissions(cmd->subtype, rel,
    > - ATT_TABLE | ATT_PARTITIONED_TABLE | ATT_FOREIGN_TABLE);
    > + ATT_TABLE | ATT_FOREIGN_TABLE);
    >  /* This command never recurses */
    > + ATPrepChangeInherit(rel);
    >  /* No command-specific prep needed */
    > 
    > That last comment seems to be a leftover, it's no longer true with this change.
    
    Deleted the comment.
    
    > 
    > tablecmds.c:17289 trailing whitespace (in the empty line)
    > /*
    > + * ALTER TABLE INHERIT
    > + *
    > + * Add a parent to the child's parents. This verifies that all the columns and
    > + * check constraints of the parent appear in the child and that they have the
    > + * same data types and expressions.
    > + *
    >  * Return the address of the new parent relation.
    >  */
    > 
    
    Deleted the whitespace.
    
    > tablecmds.c:17860 - this check in ATExecDropInherit is now redundant,
    > since we already have it in ATPrepChangeInherit
    
    No, the check is not redundant. It checks for child partitions, while ATPrepChangeInherit only blocks partitioned tables.
    
    > 
    >> Before the patch:
    >> ```
    >> evantest=# ALTER TABLE p_test CLUSTER ON idx_p_test_id;
    >> ERROR: cannot mark index clustered in partitioned table
    > 
    > Can we still reach the original error in mark_index_clustered somehow?
    > I don't see any examples in the test suite, or execution paths when I
    > have looked at the code, and it can be confusing to see a different
    > error code/message there.
    
    The portioned table check was added to mark_index_clustered with 05fb5d6. In the commit, the test case "ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;” was added as well, so my best guess the check is now no longer needed. I tried to remove the check, and “make check” still passes.
    
    However, there is a call path: vacuum -> vacuum_rel -> cluster_rel -> rebuild_relation -> mark_index_clustered. I am not sure if the check plays some role there.
    
    So, I would leave the check there, maybe use a separate discussion for removal of the check.
    
    PFA v5.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  8. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Zsolt Parragi <zsolt.parragi@percona.com> — 2026-01-26T17:26:26Z

    +ALTER TABLE nonpartitioned INHERIT partitioned; -- ok
     ERROR:  cannot inherit from partitioned table "partitioned"
     -- cannot add NO INHERIT constraint to partitioned tables
    
    That comment should be fail
    
    Otherwise the patches look good.
    
    The rest is about the two checks that seem redundant to me - I don't
    have a problem with leaving them as is, but they do seem redundant to
    me.
    
    > So, I would leave the check there, maybe use a separate discussion for removal of the check.
    
    I tried to find a way to trigger it and couldn't figure out anything,
    to me it seems unreachable.
    
    > However, there is a call path: vacuum -> vacuum_rel -> cluster_rel -> rebuild_relation -> mark_index_clustered. I am not sure if the check plays some role there.
    
    VACUUM FULL always passes InvalidOid to the cluster_rel for the index
    parameter, so we can't hit the error.
    
    CLUSTER is more difficult to follow, but to me that also seems like to
    never hit this error, and the behavior I see is also described in the
    documentation (mark_index_clustered is only called for leaf
    partitions, where it works). Following the calls in the code also
    shows the same to me, that this method is now only called for
    partitions.
    
    > No, the check is not redundant. It checks for child partitions, while ATPrepChangeInherit only blocks partitioned tables.
    
    And I have the same issue with this one: I modified that error in
    ATExecDropInherit to an assertion locally. The test suite had no new
    failures, I also tried to write a few tests manually, but I wasn't
    able to trigger it. Maybe I'm missing something, but I think it's
    redundant now.
    
    
    
    
  9. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-26T23:13:04Z

    
    > On Jan 27, 2026, at 01:26, Zsolt Parragi <zsolt.parragi@percona.com> wrote:
    > 
    > +ALTER TABLE nonpartitioned INHERIT partitioned; -- ok
    > ERROR:  cannot inherit from partitioned table "partitioned"
    > -- cannot add NO INHERIT constraint to partitioned tables
    > 
    > That comment should be fail
    
    Fixed.
    
    > 
    > Otherwise the patches look good.
    > 
    
    Thanks a lot for confirming.
    
    
    > The rest is about the two checks that seem redundant to me - I don't
    > have a problem with leaving them as is, but they do seem redundant to
    > me.
    > 
    >> So, I would leave the check there, maybe use a separate discussion for removal of the check.
    > 
    > I tried to find a way to trigger it and couldn't figure out anything,
    > to me it seems unreachable.
    > 
    >> However, there is a call path: vacuum -> vacuum_rel -> cluster_rel -> rebuild_relation -> mark_index_clustered. I am not sure if the check plays some role there.
    > 
    > VACUUM FULL always passes InvalidOid to the cluster_rel for the index
    > parameter, so we can't hit the error.
    > 
    > CLUSTER is more difficult to follow, but to me that also seems like to
    > never hit this error, and the behavior I see is also described in the
    > documentation (mark_index_clustered is only called for leaf
    > partitions, where it works). Following the calls in the code also
    > shows the same to me, that this method is now only called for
    > partitions.
    
    I will need more investigation on this, so let’s leave it for a seperate discussion.
    
    > 
    >> No, the check is not redundant. It checks for child partitions, while ATPrepChangeInherit only blocks partitioned tables.
    > 
    > And I have the same issue with this one: I modified that error in
    > ATExecDropInherit to an assertion locally. The test suite had no new
    > failures, I also tried to write a few tests manually, but I wasn't
    > able to trigger it. Maybe I'm missing something, but I think it's
    > redundant now.
    
    I added two new test cases in 0002 that trigger the check.
    
    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?
    
    PFA v6.
    
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  10. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Michael Paquier <michael@paquier.xyz> — 2026-01-27T07:48:27Z

    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.
    > 
    > 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? 
    
    There is more to this set of changes than it looks at first sight.
    
    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.
    --
    Michael
    
  11. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Zsolt Parragi <zsolt.parragi@percona.com> — 2026-01-27T08:41:09Z

    > can the previous error path in
    > mark_index_clustered() be reached through a different mean than ALTER
    > TABLE?
    
    That was one of the things we discussed in the previous emails. The
    remaining callers of that function are VACUUM FULL and CLUSTER. It
    definitely can't be hit by VACUUM (it passes an InvalidOid for an
    index), but CLUSTER is a bit more difficult to follow. It seems like
    to me that it shouldn't be hit (CLUSTER only calls it for leaf
    partitions, where the check will be false), but I'm not 100% sure
    about my diagnosis.
    
    
    
    
  12. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Zsolt Parragi <zsolt.parragi@percona.com> — 2026-01-27T08:55:23Z

    > I added two new test cases in 0002 that trigger the check.
    
    I also tested these scenarios previously. It's good that they are part
    of the test suite, but they don't hit that error path. Verified with
    this:
    
    diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    index 379f4d4ebaf..50f80724cb3 100644
    --- a/src/backend/commands/tablecmds.c
    +++ b/src/backend/commands/tablecmds.c
    @@ -17857,9 +17857,7 @@ ATExecDropInherit(Relation rel, RangeVar
    *parent, LOCKMODE lockmode)
      Relation parent_rel;
    
      if (rel->rd_rel->relispartition)
    - ereport(ERROR,
    - (errcode(ERRCODE_WRONG_OBJECT_TYPE),
    - errmsg("cannot change inheritance of a partition")));
    + Assert(0);
    
      /*
      * AccessShareLock on the parent is probably enough, seeing that DROP
    
    
    
    
  13. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-28T01:56:36Z

    
    > On Jan 27, 2026, at 15:48, Michael Paquier <michael@paquier.xyz> wrote:
    > 
    > 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.
    >> 
    >> 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?
    > 
    > There is more to this set of changes than it looks at first sight.
    > 
    > 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”. 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 … 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(). 
    
     * 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’t 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’t 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=# create table p (id int) partition by range (id);
    CREATE TABLE
    evantest=# create table p1 partition of p for values from (0) to (10);
    CREATE TABLE
    evantest=# create index p_idx on p (id);
    CREATE INDEX
    evantest=# 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 |= CLUOPT_RECHECK;
    	if (rel != NULL)
    	{
    		Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
    		check_index_is_clusterable(rel, indexOid, AccessShareLock);
    		rtcs = get_tables_to_cluster_partitioned(cluster_context, indexOid);
    
    		/* close relation, releasing lock on parent table */
    		table_close(rel, AccessExclusiveLock);
    	}
            ….
            cluster_multiple_rels(rtcs, &params); // 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’t 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 == 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/
    
    
    
    
    
    
    
    
  14. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-01-28T02:15:29Z

    
    > On Jan 27, 2026, at 16:55, Zsolt Parragi <zsolt.parragi@percona.com> wrote:
    > 
    >> I added two new test cases in 0002 that trigger the check.
    > 
    > I also tested these scenarios previously. It's good that they are part
    > of the test suite, but they don't hit that error path. Verified with
    > this:
    > 
    > diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    > index 379f4d4ebaf..50f80724cb3 100644
    > --- a/src/backend/commands/tablecmds.c
    > +++ b/src/backend/commands/tablecmds.c
    > @@ -17857,9 +17857,7 @@ ATExecDropInherit(Relation rel, RangeVar
    > *parent, LOCKMODE lockmode)
    >  Relation parent_rel;
    > 
    >  if (rel->rd_rel->relispartition)
    > - ereport(ERROR,
    > - (errcode(ERRCODE_WRONG_OBJECT_TYPE),
    > - errmsg("cannot change inheritance of a partition")));
    > + Assert(0);
    > 
    >  /*
    >  * AccessShareLock on the parent is probably enough, seeing that DROP
    
    Thank you so much for pointing out this, and sorry for misunderstanding you. You are right, as the check has been added to ATPrepChangeInherit(), the same check in ATExecDropInherit becomes redundant. I thought you were talking about the check in ATPrepChangeInherit().
    
    PFA v7:
    
    * In 0001, replaced ereport with assert in mark_index_clustered(). See my previous email for the analysis.
    * In 0002, removed the redundant check of relispartition from ATExecDropInherit().
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  15. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-03-13T03:38:17Z

    
    > On Jan 28, 2026, at 10:15, Chao Li <li.evan.chao@gmail.com> wrote:
    > 
    > 
    > 
    >> On Jan 27, 2026, at 16:55, Zsolt Parragi <zsolt.parragi@percona.com> wrote:
    >> 
    >>> I added two new test cases in 0002 that trigger the check.
    >> 
    >> I also tested these scenarios previously. It's good that they are part
    >> of the test suite, but they don't hit that error path. Verified with
    >> this:
    >> 
    >> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
    >> index 379f4d4ebaf..50f80724cb3 100644
    >> --- a/src/backend/commands/tablecmds.c
    >> +++ b/src/backend/commands/tablecmds.c
    >> @@ -17857,9 +17857,7 @@ ATExecDropInherit(Relation rel, RangeVar
    >> *parent, LOCKMODE lockmode)
    >> Relation parent_rel;
    >> 
    >> if (rel->rd_rel->relispartition)
    >> - ereport(ERROR,
    >> - (errcode(ERRCODE_WRONG_OBJECT_TYPE),
    >> - errmsg("cannot change inheritance of a partition")));
    >> + Assert(0);
    >> 
    >> /*
    >> * AccessShareLock on the parent is probably enough, seeing that DROP
    > 
    > Thank you so much for pointing out this, and sorry for misunderstanding you. You are right, as the check has been added to ATPrepChangeInherit(), the same check in ATExecDropInherit becomes redundant. I thought you were talking about the check in ATPrepChangeInherit().
    > 
    > PFA v7:
    > 
    > * In 0001, replaced ereport with assert in mark_index_clustered(). See my previous email for the analysis.
    > * In 0002, removed the redundant check of relispartition from ATExecDropInherit().
    > 
    > Best regards,
    > --
    > Chao Li (Evan)
    > HighGo Software Co., Ltd.
    > https://www.highgo.com/
    > 
    > 
    > 
    > 
    > <v7-0001-tablecmds-reject-CLUSTER-ON-for-partitioned-table.patch><v7-0002-tablecmds-reject-INHERIT-NO-INHERIT-for-partition.patch>
    
    PFA v8: rebase requested by CF https://commitfest.postgresql.org/patch/6415/
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  16. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Michael Paquier <michael@paquier.xyz> — 2026-03-16T08:51:34Z

    On Fri, Mar 13, 2026 at 11:38:17AM +0800, Chao Li wrote:
    > On Jan 28, 2026, at 10:15, Chao Li <li.evan.chao@gmail.com> wrote:
    >> * In 0001, replaced ereport with assert in
    >> mark_index_clustered(). See my previous email for the analysis. 
    
    I have looked at this one, and I think that it is right.  Even in the
    CLUSTER/VACUUM path, we have a relkind check before the sole caller of
    rebuild_relation() that discards partitioned tables, so we would never
    read mark_index_clustered() under this relkind.  Applied.
    
    >> * In 0002, removed the redundant check of relispartition from
    >> * ATExecDropInherit().
    
    I have not looked at this one.
    --
    Michael
    
  17. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-03-16T09:07:51Z

    
    > On Mar 16, 2026, at 16:51, Michael Paquier <michael@paquier.xyz> wrote:
    > 
    > On Fri, Mar 13, 2026 at 11:38:17AM +0800, Chao Li wrote:
    >> On Jan 28, 2026, at 10:15, Chao Li <li.evan.chao@gmail.com> wrote:
    >>> * In 0001, replaced ereport with assert in
    >>> mark_index_clustered(). See my previous email for the analysis.
    > 
    > I have looked at this one, and I think that it is right.  Even in the
    > CLUSTER/VACUUM path, we have a relkind check before the sole caller of
    > rebuild_relation() that discards partitioned tables, so we would never
    > read mark_index_clustered() under this relkind.  Applied.
    
    Hi Micheal, thank you very much for pushing 0001.
    
    > 
    >>> * In 0002, removed the redundant check of relispartition from
    >>> * ATExecDropInherit().
    > 
    > I have not looked at this one.
    > --
    > Michael
    
    
    Basically, 0002 does the same thing as 0001 just on a different sub-command of ALTER TABLE.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
    
    
    
  18. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Michael Paquier <michael@paquier.xyz> — 2026-03-16T22:57:57Z

    On Mon, Mar 16, 2026 at 05:07:51PM +0800, Chao Li wrote:
    > Basically, 0002 does the same thing as 0001 just on a different
    > sub-command of ALTER TABLE.
    
            case AT_DropInherit:    /* NO INHERIT */
    [...]
    -           /* No command-specific prep needed */
    +           ATPrepChangeInherit(rel);
    
    This change means that we are plugging in earlier a check based on a
    typed table for the NO INHERIT case.  This sequence fails already on
    HEAD and with the patch, but generates a different error in the last
    query between HEAD and the patch, and is not covered by your patch:
    CREATE TYPE person_type AS (id int, name text);
    CREATE TABLE persons OF person_type;
    CREATE TABLE stuff (a int);
    ALTER TABLE persons NO INHERIT stuff;
    
    I'd suggest the addition of a test in typed_table.sql, just after the
    "ALTER TABLE persons INHERIT stuff;".  The INHERIT case is already
    blocked, so NO INHERIT is a no-op anyway because we complain about the
    typed table not being inherited.  How about doing that as a separate
    patch, with the second patch for tablescmds.c updating the regression
    test output?  I thought that the NO INHERIT command was allowed, but
    we fail, so blocking it with a different, somewhat clearer error is OK
    by me.
    
    You are right that the comment on top of ATPrepAddInherit() is wrong,
    and that we'd better clean it up.  The code does not do what the
    comment tells.  That does not seem worth troubling stable branches.
    
    A second thing is that we'd better add an assertion in
    ATExecDropInherit() to make sure that this code path is never taken
    for a RELKIND_PARTITIONED_TABLE, ensuring that the prep phase blocked
    this case?
    --
    Michael
    
  19. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-03-17T01:12:13Z

    
    > On Mar 17, 2026, at 06:57, Michael Paquier <michael@paquier.xyz> wrote:
    > 
    > On Mon, Mar 16, 2026 at 05:07:51PM +0800, Chao Li wrote:
    >> Basically, 0002 does the same thing as 0001 just on a different
    >> sub-command of ALTER TABLE.
    > 
    >        case AT_DropInherit:    /* NO INHERIT */
    > [...]
    > -           /* No command-specific prep needed */
    > +           ATPrepChangeInherit(rel);
    > 
    > This change means that we are plugging in earlier a check based on a
    > typed table for the NO INHERIT case.  This sequence fails already on
    > HEAD and with the patch, but generates a different error in the last
    > query between HEAD and the patch, and is not covered by your patch:
    > CREATE TYPE person_type AS (id int, name text);
    > CREATE TABLE persons OF person_type;
    > CREATE TABLE stuff (a int);
    > ALTER TABLE persons NO INHERIT stuff;
    > 
    > I'd suggest the addition of a test in typed_table.sql, just after the
    > "ALTER TABLE persons INHERIT stuff;".  The INHERIT case is already
    > blocked, so NO INHERIT is a no-op anyway because we complain about the
    > typed table not being inherited.  How about doing that as a separate
    > patch, with the second patch for tablescmds.c updating the regression
    > test output?  I thought that the NO INHERIT command was allowed, but
    > we fail, so blocking it with a different, somewhat clearer error is OK
    > by me.
    
    Yep, my focus was only on partitioned table and ignored typed table.
    
    As your suggestion, I added a NO INHERIT test case in typed_table.sql as 0001. 
    
    > 
    > You are right that the comment on top of ATPrepAddInherit() is wrong,
    > and that we'd better clean it up.  The code does not do what the
    > comment tells.  That does not seem worth troubling stable branches.
    > 
    > A second thing is that we'd better add an assertion in
    > ATExecDropInherit() to make sure that this code path is never taken
    > for a RELKIND_PARTITIONED_TABLE, ensuring that the prep phase blocked
    > this case?
    > --
    > Michael
    
    Actually, I doubt if this assert is really needed. If we add the assert in ATExecDropInherit(), then likely we should also add the same assert in ATExecAddInherit(), since both commands now share the same preparation path via ATSimplePermissions() and ATPrepChangeInherit(). And if we assert for RELKIND_PARTITIONED_TABLE, then likely we should also assert for partitions and typed tables, since those cases are also rejected in the prep phase.
    
    But looking at other sub-commands not touched by this patch, for example:
    ```
            case AT_GenericOptions:
                  ATSimplePermissions(cmd->subtype, rel, ATT_FOREIGN_TABLE);
                  /* No command-specific prep needed */
                  pass = AT_PASS_MISC;
                  break;
    ```
    This blocks all relation kinds except ATT_FOREIGN_TABLE in the prep phase, but ATExecGenericOptions() does not assert on the table type.
    
    Anyway, I added the asserts in ATExecAddInherit() and ATExecDropInherit() in v9. If you have a second thought, I can tune it further.
    
    PFA v9:
    * 0001 - added a NO INHERIT test case in typed_table.sql
    * 0002 - added asserts in ATExecAddInherit and ATExecDropInherit.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/
    
    
    
    
    
  20. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Michael Paquier <michael@paquier.xyz> — 2026-03-17T05:36:48Z

    On Tue, Mar 17, 2026 at 09:12:13AM +0800, Chao Li wrote:
    > Anyway, I added the asserts in ATExecAddInherit() and
    > ATExecDropInherit() in v9. If you have a second thought, I can tune
    > it further.
    
    I have removed the assertions at the end, after looking at the
    surroundings for hints.
    
    > PFA v9:
    > * 0001 - added a NO INHERIT test case in typed_table.sql
    > * 0002 - added asserts in ATExecAddInherit and ATExecDropInherit.
    
    The comments rmeoved from ATPrepAddInherit() were still not at the
    correct location.  CreateInheritance() is the routine in charge of the
    column and constraint checks.
    --
    Michael
    
  21. Re: tablecmds: reject CLUSTER ON for partitioned tables earlier

    Chao Li <li.evan.chao@gmail.com> — 2026-03-17T06:14:29Z

    
    > On Mar 17, 2026, at 13:36, Michael Paquier <michael@paquier.xyz> wrote:
    > 
    > On Tue, Mar 17, 2026 at 09:12:13AM +0800, Chao Li wrote:
    >> Anyway, I added the asserts in ATExecAddInherit() and
    >> ATExecDropInherit() in v9. If you have a second thought, I can tune
    >> it further.
    > 
    > I have removed the assertions at the end, after looking at the
    > surroundings for hints.
    > 
    >> PFA v9:
    >> * 0001 - added a NO INHERIT test case in typed_table.sql
    >> * 0002 - added asserts in ATExecAddInherit and ATExecDropInherit.
    > 
    > The comments rmeoved from ATPrepAddInherit() were still not at the
    > correct location.  CreateInheritance() is the routine in charge of the
    > column and constraint checks.
    > --
    > Michael
    
    Hi Michael, thank you very much for fixing the comment and pushing the patch.
    
    Best regards,
    --
    Chao Li (Evan)
    HighGo Software Co., Ltd.
    https://www.highgo.com/