public inbox for [email protected]  
help / color / mirror / Atom feed
CLUSTER on partitioned index
29+ messages / 9 participants
[nested] [flat]

* CLUSTER on partitioned index
@ 2020-10-28 00:33  Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2020-10-28 00:33 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; pgsql-hackers <[email protected]>; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

Forking this thread, since the existing CFs have been closed.
https://www.postgresql.org/message-id/flat/20200914143102.GX18552%40telsasoft.com#58b1056488451f8594...

On Tue, Oct 06, 2020 at 01:38:23PM +0900, Michael Paquier wrote:
> On Mon, Oct 05, 2020 at 10:07:33PM -0500, Justin Pryzby wrote:
> > Honestly, I think you're over-thinking and over-engineering indisclustered.
> > 
> > If "clusteredness" was something we offered to maintain across DML, I think
> > that might be important to provide stronger guarantees.  As it is now, I don't
> > think this patch is worth changing the catalog definition.
> 
> Well, this use case is new because we are discussing the relationship
> of indisclustered across multiple transactions for multiple indexes,
> so I'd rather have this discussion than not, and I have learnt
> the hard way with REINDEX that we should care a lot about the
> consistency of partition trees at any step of the operation.

indisclustered is only used as a default for "CLUSTER" (without USING).  The
worst thing that can happen if it's "inconsistent" is that "CLUSTER;" clusters
a table on the "old" clustered index (that it was already clustered on), which
is what would've happened before running some command which was interrupted.

> Let's
> imagine a simple example here, take this partition tree: p (parent),
> and two partitions p1 and p2.  p has two partitioned indexes i and j,
> indexes also present in p1 and p2 as i1, i2, j1 and j2.  Let's assume
> that the user has done a CLUSTER on p USING i that completes, meaning
> that i, i1 and i2 have indisclustered set.  Now let's assume that the
> user does a CLUSTER on p USING j this time, and that this command
> fails while processing p2, meaning that indisclustered is set for j1,
> i2, and perhaps i or j depending on what the patch does.

I think the state of "indisclustered" at that point is not critical.
The command failed, and the user can re-run it, or ALTER..SET CLUSTER.
Actually, I think the only inconsistent state is if two indexes are both marked
indisclustered.

I'm attaching a counter-proposal to your catalog change, which preserves
indisclustered on children of clustered, partitioned indexes, and invalidates
indisclustered when attaching unclustered indexes.

Also, I noticed that CREATE TABLE (LIKE.. INCLUDING INDEXES) doesn't preserve
indisclustered, but I can't say that's an issue.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2020-11-05 02:23  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2020-11-05 02:23 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

@cfbot: rebased

On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> I'm attaching a counter-proposal to your catalog change, which preserves
> indisclustered on children of clustered, partitioned indexes, and invalidates
> indisclustered when attaching unclustered indexes.

..and now propagates CLUSTER ON to child indexes.

I left this as separate patches to show what I mean and what's new while we
discuss it.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2020-11-16 01:53  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2020-11-16 01:53 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > I'm attaching a counter-proposal to your catalog change, which preserves
> > indisclustered on children of clustered, partitioned indexes, and invalidates
> > indisclustered when attaching unclustered indexes.
> 
> ..and now propagates CLUSTER ON to child indexes.
> 
> I left this as separate patches to show what I mean and what's new while we
> discuss it.

This fixes some omissions in the previous patch and error in its test cases.

CLUSTER ON recurses to children, since I think a clustered parent index means
that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
to recurse to children, but I did it like that for consistency and it avoids
the need to special case InvalidOid.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2020-11-29 02:03  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2020-11-29 02:03 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > I'm attaching a counter-proposal to your catalog change, which preserves
> > > indisclustered on children of clustered, partitioned indexes, and invalidates
> > > indisclustered when attaching unclustered indexes.
> > 
> > ..and now propagates CLUSTER ON to child indexes.
> > 
> > I left this as separate patches to show what I mean and what's new while we
> > discuss it.
> 
> This fixes some omissions in the previous patch and error in its test cases.
> 
> CLUSTER ON recurses to children, since I think a clustered parent index means
> that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
> to recurse to children, but I did it like that for consistency and it avoids
> the need to special case InvalidOid.

The previous patch failed pg_upgrade when restoring a clustered, parent index,
since it's marked INVALID until indexes have been built on all child tables, so
CLUSTER ON was rejected on invalid index.

So I think CLUSTER ON needs to be a separate pg_dump object, to allow attaching
the child index (thereby making the parent "valid") to happen before SET
CLUSTER on the parent index.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-01-18 18:34  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2021-01-18 18:34 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

On Sat, Nov 28, 2020 at 08:03:02PM -0600, Justin Pryzby wrote:
> On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> > On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > > I'm attaching a counter-proposal to your catalog change, which preserves
> > > > indisclustered on children of clustered, partitioned indexes, and invalidates
> > > > indisclustered when attaching unclustered indexes.
> > > 
> > > ..and now propagates CLUSTER ON to child indexes.
> > > 
> > > I left this as separate patches to show what I mean and what's new while we
> > > discuss it.
> > 
> > This fixes some omissions in the previous patch and error in its test cases.
> > 
> > CLUSTER ON recurses to children, since I think a clustered parent index means
> > that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
> > to recurse to children, but I did it like that for consistency and it avoids
> > the need to special case InvalidOid.
> 
> The previous patch failed pg_upgrade when restoring a clustered, parent index,
> since it's marked INVALID until indexes have been built on all child tables, so
> CLUSTER ON was rejected on invalid index.
> 
> So I think CLUSTER ON needs to be a separate pg_dump object, to allow attaching
> the child index (thereby making the parent "valid") to happen before SET
> CLUSTER on the parent index.

Rebased on b5913f612 and now a3dc92600.

This patch is intertwined with the tablespace patch: not only will it get
rebase conflict, but will also need to test the functionality of
CLUSTER (TABLESPACE a) partitioned_table;

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-02-06 14:45  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 2 replies; 29+ messages in thread

From: Justin Pryzby @ 2021-02-06 14:45 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

On Mon, Jan 18, 2021 at 12:34:59PM -0600, Justin Pryzby wrote:
> On Sat, Nov 28, 2020 at 08:03:02PM -0600, Justin Pryzby wrote:
> > On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> > > On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > > > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > > > I'm attaching a counter-proposal to your catalog change, which preserves
> > > > > indisclustered on children of clustered, partitioned indexes, and invalidates
> > > > > indisclustered when attaching unclustered indexes.
> > > > 
> > > > ..and now propagates CLUSTER ON to child indexes.
> > > > 
> > > > I left this as separate patches to show what I mean and what's new while we
> > > > discuss it.
> > > 
> > > This fixes some omissions in the previous patch and error in its test cases.
> > > 
> > > CLUSTER ON recurses to children, since I think a clustered parent index means
> > > that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
> > > to recurse to children, but I did it like that for consistency and it avoids
> > > the need to special case InvalidOid.
> > 
> > The previous patch failed pg_upgrade when restoring a clustered, parent index,
> > since it's marked INVALID until indexes have been built on all child tables, so
> > CLUSTER ON was rejected on invalid index.
> > 
> > So I think CLUSTER ON needs to be a separate pg_dump object, to allow attaching
> > the child index (thereby making the parent "valid") to happen before SET
> > CLUSTER on the parent index.
> 
> Rebased on b5913f612 and now a3dc92600.

This resolves ORDER BY test failure with COLLATE "C".

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-02-06 17:21  Zhihong Yu <[email protected]>
  parent: Justin Pryzby <[email protected]>
  1 sibling, 0 replies; 29+ messages in thread

From: Zhihong Yu @ 2021-02-06 17:21 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; PostgreSQL Developers <[email protected]>; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

Hi,
For v7-0002-Implement-CLUSTER-of-partitioned-table.patch:

+        * We have to build the list in a different memory context so it
will
+        * survive the cross-transaction processing
+        */
+       old_context = MemoryContextSwitchTo(cluster_context);

cluster_context is not modified within the loop. Can the memory context
switching code be moved outside the loop ?

Cheers

On Sat, Feb 6, 2021 at 6:46 AM Justin Pryzby <[email protected]> wrote:

> On Mon, Jan 18, 2021 at 12:34:59PM -0600, Justin Pryzby wrote:
> > On Sat, Nov 28, 2020 at 08:03:02PM -0600, Justin Pryzby wrote:
> > > On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> > > > On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > > > > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > > > > I'm attaching a counter-proposal to your catalog change, which
> preserves
> > > > > > indisclustered on children of clustered, partitioned indexes,
> and invalidates
> > > > > > indisclustered when attaching unclustered indexes.
> > > > >
> > > > > ..and now propagates CLUSTER ON to child indexes.
> > > > >
> > > > > I left this as separate patches to show what I mean and what's new
> while we
> > > > > discuss it.
> > > >
> > > > This fixes some omissions in the previous patch and error in its
> test cases.
> > > >
> > > > CLUSTER ON recurses to children, since I think a clustered parent
> index means
> > > > that all its child indexes are clustered.  "SET WITHOUT CLUSTER"
> doesn't have
> > > > to recurse to children, but I did it like that for consistency and
> it avoids
> > > > the need to special case InvalidOid.
> > >
> > > The previous patch failed pg_upgrade when restoring a clustered,
> parent index,
> > > since it's marked INVALID until indexes have been built on all child
> tables, so
> > > CLUSTER ON was rejected on invalid index.
> > >
> > > So I think CLUSTER ON needs to be a separate pg_dump object, to allow
> attaching
> > > the child index (thereby making the parent "valid") to happen before
> SET
> > > CLUSTER on the parent index.
> >
> > Rebased on b5913f612 and now a3dc92600.
>
> This resolves ORDER BY test failure with COLLATE "C".
>
> --
> Justin
>


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-02-10 20:04  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  1 sibling, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2021-02-10 20:04 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

On Sat, Feb 06, 2021 at 08:45:49AM -0600, Justin Pryzby wrote:
> On Mon, Jan 18, 2021 at 12:34:59PM -0600, Justin Pryzby wrote:
> > On Sat, Nov 28, 2020 at 08:03:02PM -0600, Justin Pryzby wrote:
> > > On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> > > > On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > > > > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > > > > I'm attaching a counter-proposal to your catalog change, which preserves
> > > > > > indisclustered on children of clustered, partitioned indexes, and invalidates
> > > > > > indisclustered when attaching unclustered indexes.
> > > > > 
> > > > > ..and now propagates CLUSTER ON to child indexes.
> > > > > 
> > > > > I left this as separate patches to show what I mean and what's new while we
> > > > > discuss it.
> > > > 
> > > > This fixes some omissions in the previous patch and error in its test cases.
> > > > 
> > > > CLUSTER ON recurses to children, since I think a clustered parent index means
> > > > that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
> > > > to recurse to children, but I did it like that for consistency and it avoids
> > > > the need to special case InvalidOid.
> > > 
> > > The previous patch failed pg_upgrade when restoring a clustered, parent index,
> > > since it's marked INVALID until indexes have been built on all child tables, so
> > > CLUSTER ON was rejected on invalid index.
> > > 
> > > So I think CLUSTER ON needs to be a separate pg_dump object, to allow attaching
> > > the child index (thereby making the parent "valid") to happen before SET
> > > CLUSTER on the parent index.
> > 
> > Rebased on b5913f612 and now a3dc92600.
> 
> This resolves ORDER BY test failure with COLLATE "C".

It occured to me that progress reporting should expose this.

I did this in the style of pg_stat_progress_create_index, adding columns
partitions_total and partitions_done showing the overall progress. The progress
of individual partitions is also visible in {blocks,tuples}_{done,total}.
This seems odd, but that's how the index view behaves.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-03-07 04:13  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2021-03-07 04:13 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>; Zhihong Yu <[email protected]>

On Wed, Feb 10, 2021 at 02:04:58PM -0600, Justin Pryzby wrote:
> On Sat, Feb 06, 2021 at 08:45:49AM -0600, Justin Pryzby wrote:
> > On Mon, Jan 18, 2021 at 12:34:59PM -0600, Justin Pryzby wrote:
> > > On Sat, Nov 28, 2020 at 08:03:02PM -0600, Justin Pryzby wrote:
> > > > On Sun, Nov 15, 2020 at 07:53:35PM -0600, Justin Pryzby wrote:
> > > > > On Wed, Nov 04, 2020 at 08:23:56PM -0600, Justin Pryzby wrote:
> > > > > > On Tue, Oct 27, 2020 at 07:33:12PM -0500, Justin Pryzby wrote:
> > > > > > > I'm attaching a counter-proposal to your catalog change, which preserves
> > > > > > > indisclustered on children of clustered, partitioned indexes, and invalidates
> > > > > > > indisclustered when attaching unclustered indexes.
> > > > > > 
> > > > > > ..and now propagates CLUSTER ON to child indexes.
> > > > > > 
> > > > > > I left this as separate patches to show what I mean and what's new while we
> > > > > > discuss it.
> > > > > 
> > > > > This fixes some omissions in the previous patch and error in its test cases.
> > > > > 
> > > > > CLUSTER ON recurses to children, since I think a clustered parent index means
> > > > > that all its child indexes are clustered.  "SET WITHOUT CLUSTER" doesn't have
> > > > > to recurse to children, but I did it like that for consistency and it avoids
> > > > > the need to special case InvalidOid.
> > > > 
> > > > The previous patch failed pg_upgrade when restoring a clustered, parent index,
> > > > since it's marked INVALID until indexes have been built on all child tables, so
> > > > CLUSTER ON was rejected on invalid index.
> > > > 
> > > > So I think CLUSTER ON needs to be a separate pg_dump object, to allow attaching
> > > > the child index (thereby making the parent "valid") to happen before SET
> > > > CLUSTER on the parent index.
> > > 
> > > Rebased on b5913f612 and now a3dc92600.
> > 
> > This resolves ORDER BY test failure with COLLATE "C".
> 
> It occured to me that progress reporting should expose this.
> 
> I did this in the style of pg_stat_progress_create_index, adding columns
> partitions_total and partitions_done showing the overall progress. The progress
> of individual partitions is also visible in {blocks,tuples}_{done,total}.
> This seems odd, but that's how the index view behaves.

Rebased on 8a8f4d8ede288c2a29105f4708e22ce7f3526149.

This also resolves an issue in the last patch which would've broken progress
reporting of vacuum full.

And take the suggestion to move memory context switching outside the loop.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-04-02 20:03  Justin Pryzby <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 2 replies; 29+ messages in thread

From: Justin Pryzby @ 2021-04-02 20:03 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>; Zhihong Yu <[email protected]>

@cfbot: rebased


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-04-03 16:14  Zhihong Yu <[email protected]>
  parent: Justin Pryzby <[email protected]>
  1 sibling, 0 replies; 29+ messages in thread

From: Zhihong Yu @ 2021-04-03 16:14 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; PostgreSQL Developers <[email protected]>; 曾文旌(义从) <[email protected]>; Alvaro Herrera <[email protected]>

Hi,
For v10-0002-Implement-CLUSTER-of-partitioned-table.patch :

or that an partitioned index was previously set clustered.

'an partitioned index' -> a partitioned index

+ * Return a List of tables and associated index, where each index is a

associated index -> associated indices

For cluster():
-       rel = table_open(tableOid, NoLock);
+       rel = table_open(tableOid, ShareUpdateExclusiveLock);

Considering the comment preceding cluster() (forced to acquire exclusive
locks on all the tables), maybe add a comment explaining why it is safe to
take ShareUpdateExclusiveLock.

+cluster_multiple_rels(List *rvs, int options)

I think the multiple in the method name is not needed since the relation is
in plural.

Cheers

On Fri, Apr 2, 2021 at 1:03 PM Justin Pryzby <[email protected]> wrote:

> @cfbot: rebased
>


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* [PATCH v1] Fix SQL-standard body empty statements handling.
@ 2021-06-06 07:28  Julien Rouhaud <[email protected]>
  0 siblings, 0 replies; 29+ messages in thread

From: Julien Rouhaud @ 2021-06-06 07:28 UTC (permalink / raw)

---
 src/backend/commands/functioncmds.c             | 3 +++
 src/test/regress/expected/create_function_3.out | 2 +-
 src/test/regress/sql/create_function_3.sql      | 2 +-
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 4c12aa33df..61de9bf11a 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -932,6 +932,9 @@ interpret_AS_clause(Oid languageOid, const char *languageName,
 				Query	   *q;
 				ParseState *pstate = make_parsestate(NULL);
 
+				if (!stmt)
+					continue;
+
 				pstate->p_sourcetext = queryString;
 				sql_fn_parser_setup(pstate, pinfo);
 				q = transformStmt(pstate, stmt);
diff --git a/src/test/regress/expected/create_function_3.out b/src/test/regress/expected/create_function_3.out
index 5b6bc5eddb..5955859bb5 100644
--- a/src/test/regress/expected/create_function_3.out
+++ b/src/test/regress/expected/create_function_3.out
@@ -267,7 +267,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
     RETURN false;
 CREATE FUNCTION functest_S_3a() RETURNS boolean
     BEGIN ATOMIC
-        RETURN false;
+        ;;RETURN false;;
     END;
 CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
     LANGUAGE SQL
diff --git a/src/test/regress/sql/create_function_3.sql b/src/test/regress/sql/create_function_3.sql
index 4b778999ed..6e8b838ff2 100644
--- a/src/test/regress/sql/create_function_3.sql
+++ b/src/test/regress/sql/create_function_3.sql
@@ -165,7 +165,7 @@ CREATE FUNCTION functest_S_3() RETURNS boolean
     RETURN false;
 CREATE FUNCTION functest_S_3a() RETURNS boolean
     BEGIN ATOMIC
-        RETURN false;
+        ;;RETURN false;;
     END;
 
 CREATE FUNCTION functest_S_10(a text, b date) RETURNS boolean
-- 
2.31.1


--5raazt6dt2qmoyv7--





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-07-21 00:27  Alvaro Herrera <[email protected]>
  parent: Justin Pryzby <[email protected]>
  1 sibling, 4 replies; 29+ messages in thread

From: Alvaro Herrera @ 2021-07-21 00:27 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

I have to wonder if there really *is* a use case for CLUSTER in the
first place on regular tables, let alone on partitioned tables, which
are likely to be large and thus take a lot of time.  What justifies
spending so much time on this implementation?  My impression is that
CLUSTER is pretty much a fringe command nowadays, because of the access
exclusive lock required.

Does anybody actually use it?

-- 
Álvaro Herrera           39°49'30"S 73°17'W  —  https://www.EnterpriseDB.com/
"Ninguna manada de bestias tiene una voz tan horrible como la humana" (Orual)





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-07-21 00:34  Michael Paquier <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  3 siblings, 0 replies; 29+ messages in thread

From: Michael Paquier @ 2021-07-21 00:34 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Tue, Jul 20, 2021 at 08:27:02PM -0400, Alvaro Herrera wrote:
> I have to wonder if there really *is* a use case for CLUSTER in the
> first place on regular tables, let alone on partitioned tables, which
> are likely to be large and thus take a lot of time.  What justifies
> spending so much time on this implementation?  My impression is that
> CLUSTER is pretty much a fringe command nowadays, because of the access
> exclusive lock required.
> 
> Does anybody actually use it?

Yeah, I am not getting really excited about doing anything here
either.  I thought for some time about the interactions with
indisclustered and partitioned tables, but anything I could come up
with felt clunky.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../YPdrhFyCQqJ%[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-07-21 11:01  Laurenz Albe <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  3 siblings, 0 replies; 29+ messages in thread

From: Laurenz Albe @ 2021-07-21 11:01 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Tue, 2021-07-20 at 20:27 -0400, Alvaro Herrera wrote:
> I have to wonder if there really *is* a use case for CLUSTER in the
> first place on regular tables, let alone on partitioned tables, which
> are likely to be large and thus take a lot of time.  What justifies
> spending so much time on this implementation?  My impression is that
> CLUSTER is pretty much a fringe command nowadays, because of the access
> exclusive lock required.
> 
> Does anybody actually use it?

I see is used in the field occasionally, as it can really drastically
improve performance.  But I admit is is not frequently used.

In a data warehouse, which is updated only occasionally, running
CLUSTER after an update can make a lot of sense.

I personally think that it is enough to be able to cluster the table
partiton by partition.

Yours,
Laurenz Albe






^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-09-12 20:10  Justin Pryzby <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  3 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2021-09-12 20:10 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Tue, Jul 20, 2021 at 08:27:02PM -0400, Alvaro Herrera wrote:
> I have to wonder if there really *is* a use case for CLUSTER in the
> first place on regular tables, let alone on partitioned tables, which
> are likely to be large and thus take a lot of time.

The cluster now is done one partition at a time, so it might take a long time,
but doesn't lock the whole partition heirarchy.  Same as VACUUM (since v10) and
(since v14) REINDEX.

The patch series would be simpler if partitioned indexes weren't allowed to be
marked CLUSTERED ON.  Then, "USING <index>" would be required, which is a step
forward from not supporting cluster on partitioned index at all.  As attached.
It's arguably true that the follow-up patches supporting indisclustered on
partitioned indexes aren't worth the trouble.

For sure CLUSTER is useful, see eg.
https://github.com/bucardo/check_postgres/issues/29

It's sometimes important that the table is clustered to allow index scan to
work well (or be chosen at all).

If a table is scanned by an index, and isn't well-clustered, then a larger
fraction (multiple) of the table will be read than what's optimal.   That
requires more IO, and more cache space.

A year ago, I partitioned one of our previously-unpartitioned tables, and ended
up clustering the partitions on their partition key (and indexed column) using
\gexec.  This was preferable to doing INSERT .. SELECT .. ORDER BY, which
would've made the initial process slower - maybe unjustifiably slower for some
customers.  Cluster (using \gexec) was something I was able to do afterward,
for completeness, since I expect the partitions to be mostly-clustered
automatically, so it was bothering me that the existing data was unordered, and
that it might behave differently in the future.

> What justifies spending so much time on this implementation?

Actually, I don't use partitioned indexes at all here, so this is not for us..
I worked on this after Adger asked about CIC on partitioned tables (for which I
have a patch in the queue).  Isn't it worth supporting that (or should we
include an example about how to use format() with %I and \gexec) ?

VACUUM [FULL] has recursed into partitions since v10 (f0e44751d).
REINDEX supports partitioned tables in v14 (a6642b3ae).  
Partitioned indexes exist since v11 (as you well know), so it's somewhat odd
that CLUSTER isn't supported, and seems increasingly weird as decreasing number
of DDL commands are not supported.  Supporting DDL on partitioned tables
supports the idea that the physical partitions can be seen as an implementation
detail by the DBA, which I understand was the intent since v10.

You're right that I wouldn't plan to *routinely* re-cluster a partitioned
table.  Rather, I'd cluster only its "recent" *partitions*, and leave the old
ones alone.  Or cluster the partitions, a single time, once they're no longer
recent.  I don't think the feature is marginal just because I don't use it
routinely.

> My impression is that CLUSTER is pretty much a fringe command nowadays,
> because of the access exclusive lock required.

A step forward would be to integrate something like pg_repack/reorg/squeeze.
I used pg_repack --index until v12 got REINDEX CONCURRENTLY.  The goal there
was to improve index scans on some large, append-only partitions where the
planner gave an index scan, but performance was poor (now, we use BRIN so it
works well without reindex).  I tested that this would still be an issue by
creating a non-brin index for a single day's table (even with v13 deduplication
and v12 TID tiebreak).

As I see it, support for partitioned cluster is orthogonal to an
online/concurrent cluster, which is a job for another patch.

-- 
Justin


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-09-23 18:18  Matthias van de Meent <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Matthias van de Meent @ 2021-09-23 18:18 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; PostgreSQL Hackers <[email protected]>; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Sun, 12 Sept 2021 at 22:10, Justin Pryzby <[email protected]> wrote:
>
> On Tue, Jul 20, 2021 at 08:27:02PM -0400, Alvaro Herrera wrote:
> > I have to wonder if there really *is* a use case for CLUSTER in the
> > first place on regular tables, let alone on partitioned tables, which
> > are likely to be large and thus take a lot of time.
>
> The cluster now is done one partition at a time, so it might take a long time,
> but doesn't lock the whole partition heirarchy.  Same as VACUUM (since v10) and
> (since v14) REINDEX.

Note: The following review is based on the assumption that this v11
revision was meant to contain only one patch. I put this up as a note,
because it seemed quite limited when compared to earlier versions of
the patchset.

I noticed that you store the result of find_all_inheritors(...,
NoLock) in get_tables_to_cluster_partitioned, without taking care of
potential concurrent partition hierarchy changes that the comment on
find_all_inheritors warns against or documenting why it is safe, which
sounds dangerous in case someone wants to adapt the code. One problem
I can think of is that only storing reloid and indoid is not
necessarily safe, as they might be reused by drop+create table running
parallel to the CLUSTER command.

Apart from that, I think it would be useful (though not strictly
necessary for this patch) if you could adapt the current CLUSTER
progress reporting to report the progress for the whole partition
hierarchy, instead of a new progress report for each relation, as was
the case in earlier versions of the patchset.

The v11 patch seems quite incomplete when compared to previous
discussions, or at the very least is very limited (no ALTER TABLE
clustering commands for partitioned tables, but `CLUSTER ptable USING
pindex` is supported). If v11 is the new proposed direction for ptable
clustering, could you also document these limitations in the
cluster.sgml and alter_table.sgml docs?

> [ v11-0001-Implement-CLUSTER-of-partitioned-table.patch ]

> diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
> ...
> +ALTER TABLE clstrpart SET WITHOUT CLUSTER;
> +ERROR:  cannot mark index clustered in partitioned table

This error message does not seem to match my expectation as a user: I
am not trying to mark an index as clustered, and for a normal table
"SET WITHOUT CLUSTER" does not fail for unclustered tables. I think
that behaviour of normal unclustered tables should be shared here as
well. At the very least, the error message should be changed.

>  ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
>  ERROR:  cannot mark index clustered in partitioned table

A "HINT: use the CLUSTER command to cluster partitioned tables" (or
equivalent) should be added if we decide to keep the clustering APIs
of ALTER TABLE disabled for partitioned tables, as CLUSTER is now
implemented for partitioned tables.

> -DROP TABLE clstrpart;

I believe that this cleanup should not be fully removed, but moved to
before '-- Test CLUSTER with external tuplesorting', as the table is
not used after that line.

Kind regards,

Matthias van de Meent





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-09-23 23:56  Justin Pryzby <[email protected]>
  parent: Matthias van de Meent <[email protected]>
  0 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2021-09-23 23:56 UTC (permalink / raw)
  To: Matthias van de Meent <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Thu, Sep 23, 2021 at 08:18:41PM +0200, Matthias van de Meent wrote:
> On Sun, 12 Sept 2021 at 22:10, Justin Pryzby <[email protected]> wrote:
> >
> > On Tue, Jul 20, 2021 at 08:27:02PM -0400, Alvaro Herrera wrote:
> > > I have to wonder if there really *is* a use case for CLUSTER in the
> > > first place on regular tables, let alone on partitioned tables, which
> > > are likely to be large and thus take a lot of time.
> >
> > The cluster now is done one partition at a time, so it might take a long time,
> > but doesn't lock the whole partition heirarchy.  Same as VACUUM (since v10) and
> > (since v14) REINDEX.
> 
> Note: The following review is based on the assumption that this v11
> revision was meant to contain only one patch. I put this up as a note,
> because it seemed quite limited when compared to earlier versions of
> the patchset.

Alvaro's critique was that the patchset was too complicated for what was
claimed to be a marginal feature.  My response was to rearrange the patchset to
its minimal form, supporting CLUSTER without marking the index as clustered.

> I noticed that you store the result of find_all_inheritors(...,
> NoLock) in get_tables_to_cluster_partitioned, without taking care of
> potential concurrent partition hierarchy changes that the comment on
> find_all_inheritors warns against or documenting why it is safe, which
> sounds dangerous in case someone wants to adapt the code. One problem
> I can think of is that only storing reloid and indoid is not
> necessarily safe, as they might be reused by drop+create table running
> parallel to the CLUSTER command.

The parallel code in vacuum is expand_vacuum_rel(), which is where the
corresponding things happens for vacuum full.  This patch is to make cluster()
do all the same stuff before calling cluster_rel().

What VACUUM tries to do is to avoid erroring if a partition is dropped while
cluster is running.  cluster_rel() does the same thing by calling
cluster_multiple_rels() ,which uses CLUOPT_RECHECK.

If the OIDs wrapped around, I think existing vacuum could accidentally process
a new table with the same OID as a dropped partition.  I think cluster would
*normally* catch that case and error in check_index_is_clusterable():
| Check that index is in fact an index on the given relation

Arguably VACUUM FULL could call cluster() (not cluster_rel()) and pass the
partitioned table rather than first expanding it.  But non-full vacuum needs to 
expand partitioned tables anyway.

> Apart from that, I think it would be useful (though not strictly
> necessary for this patch) if you could adapt the current CLUSTER
> progress reporting to report the progress for the whole partition
> hierarchy, instead of a new progress report for each relation, as was
> the case in earlier versions of the patchset.

Yea, but this is already true for VACUUM FULL (which uses CLUSTER and supports
partitioned tables since v10) and REINDEX.
See also https://postgr.es/m/[email protected]

My goal is to present a minimal patch and avoid any nonessential complexity.

> The v11 patch seems quite incomplete when compared to previous
> discussions, or at the very least is very limited (no ALTER TABLE
> clustering commands for partitioned tables, but `CLUSTER ptable USING
> pindex` is supported). If v11 is the new proposed direction for ptable
> clustering, could you also document these limitations in the
> cluster.sgml and alter_table.sgml docs?

You said it's less complete, but it's is due to deliberate reduction in scope.
cluster.sgml says:
+    Clustering a partitioned table clusters each of its partitions using the
+    partition of the specified partitioned index (which must be specified).

The ALTER restriction hasn't changed, so I didn't touch the documentation.

I am still curious myself to know if this is the direction the patch should
move.

> > [ v11-0001-Implement-CLUSTER-of-partitioned-table.patch ]
> 
> > diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
> > ...
> > +ALTER TABLE clstrpart SET WITHOUT CLUSTER;
> > +ERROR:  cannot mark index clustered in partitioned table
> 
> This error message does not seem to match my expectation as a user: I
> am not trying to mark an index as clustered, and for a normal table
> "SET WITHOUT CLUSTER" does not fail for unclustered tables. I think
> that behaviour of normal unclustered tables should be shared here as
> well. At the very least, the error message should be changed.

This is the pre-existing behavior.

> > -DROP TABLE clstrpart;
> 
> I believe that this cleanup should not be fully removed, but moved to
> before '-- Test CLUSTER with external tuplesorting', as the table is
> not used after that line.

You're right - this was from when the patchset handled CLUSTER ON.
Leaving the index allows testing in pg_dump - a large part of the complexity of
the elided patches is to handle restoring a partitioned index, without
violating the rule that partitions of an clustered index must also be
clustered.  I adjusted this in my local branch.

Thanks for looking.  I'm going to see about updating comments based on
corresponding parts of vacuum and on this message itself.

-- 
Justin





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2021-12-03 01:16  Michael Paquier <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 0 replies; 29+ messages in thread

From: Michael Paquier @ 2021-12-03 01:16 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Matthias van de Meent <[email protected]>; Alvaro Herrera <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Thu, Sep 23, 2021 at 06:56:26PM -0500, Justin Pryzby wrote:
> On Thu, Sep 23, 2021 at 08:18:41PM +0200, Matthias van de Meent wrote:
>> Note: The following review is based on the assumption that this v11
>> revision was meant to contain only one patch. I put this up as a note,
>> because it seemed quite limited when compared to earlier versions of
>> the patchset.
> 
> Alvaro's critique was that the patchset was too complicated for what was
> claimed to be a marginal feature.  My response was to rearrange the patchset to
> its minimal form, supporting CLUSTER without marking the index as clustered.
>
> My goal is to present a minimal patch and avoid any nonessential complexity.

FWIW, my opinion on the matter is similar to Alvaro's, and an extra
read of the patch gives me the same impression.  Let's see if others
have an opinion on the matter.

> Thanks for looking.  I'm going to see about updating comments based on
> corresponding parts of vacuum and on this message itself.

It doesn't feel right to just discard the patch at this stage, and it
needs an update, so I have moved it to the next CF for now, waiting on
author.  If this does not really move on, my suggestion is to discard
the patch at the end of next CF, aka 2022-01.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-02-23 18:47  Justin Pryzby <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  3 siblings, 1 reply; 29+ messages in thread

From: Justin Pryzby @ 2022-02-23 18:47 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Tue, Jul 20, 2021 at 08:27:02PM -0400, Alvaro Herrera wrote:
> I have to wonder if there really *is* a use case for CLUSTER in the
> first place on regular tables, let alone on partitioned tables, which
> are likely to be large and thus take a lot of time.  What justifies
> spending so much time on this implementation?  My impression is that
> CLUSTER is pretty much a fringe command nowadays, because of the access
> exclusive lock required.
> 
> Does anybody actually use it?

I hope that Alvaro will comment on the simplified patches.  If multiple people
think the patch isn't worth it, feel free to close it.  But I don't see how
complexity could be the reason.

-- 
Justin






^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-03-30 20:51  Alvaro Herrera <[email protected]>
  parent: Justin Pryzby <[email protected]>
  0 siblings, 2 replies; 29+ messages in thread

From: Alvaro Herrera @ 2022-03-30 20:51 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On 2022-Feb-23, Justin Pryzby wrote:

> I hope that Alvaro will comment on the simplified patches.  If multiple people
> think the patch isn't worth it, feel free to close it.  But I don't see how
> complexity could be the reason.

I gave your patch a look and it seems a reasonable thing to do.  Maybe
not terribly useful in most cases, but there may be some cases for which
it is.  I found some part of it a bit repetitive, so I moved things
around a bit.  What do think about this?

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"After a quick R of TFM, all I can say is HOLY CR** THAT IS COOL! PostgreSQL was
amazing when I first started using it at 7.2, and I'm continually astounded by
learning new features and techniques made available by the continuing work of
the development team."
Berend Tober, http://archives.postgresql.org/pgsql-hackers/2007-08/msg01009.php


Attachments:

  [text/x-diff] clustering-partitioned-tables.patch (19.9K, ../../[email protected]/2-clustering-partitioned-tables.patch)
  download | inline diff:
diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index 86f5fdc469..b3463ae5c4 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -196,6 +196,12 @@ CLUSTER [VERBOSE]
     in the <structname>pg_stat_progress_cluster</structname> view. See
     <xref linkend="cluster-progress-reporting"/> for details.
   </para>
+
+   <para>
+    Clustering a partitioned table clusters each of its partitions using the
+    partition of the specified partitioned index (which must be specified).
+   </para>
+
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 02a7e94bf9..8417cbdb67 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -32,7 +32,9 @@
 #include "catalog/index.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/partition.h"
 #include "catalog/pg_am.h"
+#include "catalog/pg_inherits.h"
 #include "catalog/toasting.h"
 #include "commands/cluster.h"
 #include "commands/defrem.h"
@@ -73,6 +75,9 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
 							bool verbose, bool *pSwapToastByContent,
 							TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
+static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context,
+											   Oid indexOid);
+static void cluster_multiple_rels(List *rvs, ClusterParams *params);
 
 
 /*---------------------------------------------------------------------------
@@ -105,6 +110,10 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
 	ListCell   *lc;
 	ClusterParams params = {0};
 	bool		verbose = false;
+	Relation	rel = NULL;
+	Oid			indexOid = InvalidOid;
+	MemoryContext cluster_context;
+	List	   *rtcs;
 
 	/* Parse option list */
 	foreach(lc, stmt->params)
@@ -126,11 +135,13 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
 	if (stmt->relation != NULL)
 	{
 		/* This is the single-relation case. */
-		Oid			tableOid,
-					indexOid = InvalidOid;
-		Relation	rel;
+		Oid			tableOid;
 
-		/* Find, lock, and check permissions on the table */
+		/*
+		 * Find, lock, and check permissions on the table.  We obtain
+		 * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
+		 * single-transaction case.
+		 */
 		tableOid = RangeVarGetRelidExtended(stmt->relation,
 											AccessExclusiveLock,
 											0,
@@ -146,14 +157,6 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot cluster temporary tables of other sessions")));
 
-		/*
-		 * Reject clustering a partitioned table.
-		 */
-		if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("cannot cluster a partitioned table")));
-
 		if (stmt->indexname == NULL)
 		{
 			ListCell   *index;
@@ -188,71 +191,100 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
 								stmt->indexname, stmt->relation->relname)));
 		}
 
-		/* close relation, keep lock till commit */
-		table_close(rel, NoLock);
+		if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
+		{
+			/* close relation, keep lock till commit */
+			table_close(rel, NoLock);
 
-		/* Do the job. */
-		cluster_rel(tableOid, indexOid, &params);
+			/* Do the job. */
+			cluster_rel(tableOid, indexOid, &params);
+
+			return;
+		}
+	}
+
+	/*
+	 * By here, we know we are in a multi-table situation.  In order to avoid
+	 * holding locks for too long, we want to process each table in its own
+	 * transaction.  This forces us to disallow running inside a user
+	 * transaction block.
+	 */
+	PreventInTransactionBlock(isTopLevel, "CLUSTER");
+
+	/* Also, we need a memory context to hold our list of relations */
+	cluster_context = AllocSetContextCreate(PortalContext,
+											"Cluster",
+											ALLOCSET_DEFAULT_SIZES);
+
+	/*
+	 * 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)
+	{
+		check_index_is_clusterable(rel, indexOid, true, AccessShareLock);
+		rtcs = get_tables_to_cluster_partitioned(cluster_context, indexOid);
+
+		/* close relation, releasing lock on parent table */
+		table_close(rel, AccessExclusiveLock);
 	}
 	else
 	{
-		/*
-		 * This is the "multi relation" case. We need to cluster all tables
-		 * that have some index with indisclustered set.
-		 */
-		MemoryContext cluster_context;
-		List	   *rvs;
-		ListCell   *rv;
+		rtcs = get_tables_to_cluster(cluster_context);
+		params.options |= CLUOPT_RECHECK_ISCLUSTERED;
+	}
 
-		/*
-		 * We cannot run this form of CLUSTER inside a user transaction block;
-		 * we'd be holding locks way too long.
-		 */
-		PreventInTransactionBlock(isTopLevel, "CLUSTER");
+	/* Do the job. */
+	cluster_multiple_rels(rtcs, &params);
 
-		/*
-		 * Create special memory context for cross-transaction storage.
-		 *
-		 * Since it is a child of PortalContext, it will go away even in case
-		 * of error.
-		 */
-		cluster_context = AllocSetContextCreate(PortalContext,
-												"Cluster",
-												ALLOCSET_DEFAULT_SIZES);
+	/* Start a new transaction for the cleanup work. */
+	StartTransactionCommand();
 
-		/*
-		 * Build the list of relations to cluster.  Note that this lives in
-		 * cluster_context.
-		 */
-		rvs = get_tables_to_cluster(cluster_context);
+	/* Clean up working storage */
+	MemoryContextDelete(cluster_context);
+}
 
-		/* Commit to get out of starting transaction */
-		PopActiveSnapshot();
-		CommitTransactionCommand();
+/*
+ * Given a list of relations to cluster, process each of them in a separate
+ * transaction.
+ *
+ * We expect to be in a transaction at start, but there isn't one when we
+ * return.
+ */
+static void
+cluster_multiple_rels(List *rvs, ClusterParams *params)
+{
+	ListCell   *lc;
 
-		/* Ok, now that we've got them all, cluster them one by one */
-		foreach(rv, rvs)
-		{
-			RelToCluster *rvtc = (RelToCluster *) lfirst(rv);
-			ClusterParams cluster_params = params;
+	/* Commit to get out of starting transaction */
+	PopActiveSnapshot();
+	CommitTransactionCommand();
 
-			/* Start a new transaction for each relation. */
-			StartTransactionCommand();
-			/* functions in indexes may want a snapshot set */
-			PushActiveSnapshot(GetTransactionSnapshot());
-			/* Do the job. */
-			cluster_params.options |= CLUOPT_RECHECK;
-			cluster_rel(rvtc->tableOid, rvtc->indexOid,
-						&cluster_params);
-			PopActiveSnapshot();
-			CommitTransactionCommand();
-		}
+	/* Cluster the tables, each in a separate transaction */
+	foreach(lc, rvs)
+	{
+		RelToCluster *rvtc = (RelToCluster *) lfirst(lc);
 
-		/* Start a new transaction for the cleanup work. */
+		/* Start a new transaction for each relation. */
 		StartTransactionCommand();
 
-		/* Clean up working storage */
-		MemoryContextDelete(cluster_context);
+		/* functions in indexes may want a snapshot set */
+		PushActiveSnapshot(GetTransactionSnapshot());
+
+		/* Do the job. */
+		cluster_rel(rvtc->tableOid, rvtc->indexOid, params);
+
+		PopActiveSnapshot();
+		CommitTransactionCommand();
 	}
 }
 
@@ -327,10 +359,11 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 		/*
 		 * Silently skip a temp table for a remote session.  Only doing this
 		 * check in the "recheck" case is appropriate (which currently means
-		 * somebody is executing a database-wide CLUSTER), because there is
-		 * another check in cluster() which will stop any attempt to cluster
-		 * remote temp tables by name.  There is another check in cluster_rel
-		 * which is redundant, but we leave it for extra safety.
+		 * somebody is executing a database-wide CLUSTER or on a partitioned
+		 * table), because there is another check in cluster() which will stop
+		 * any attempt to cluster remote temp tables by name.  There is
+		 * another check in cluster_rel which is redundant, but we leave it
+		 * for extra safety.
 		 */
 		if (RELATION_IS_OTHER_TEMP(OldHeap))
 		{
@@ -352,9 +385,11 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 			}
 
 			/*
-			 * Check that the index is still the one with indisclustered set.
+			 * Check that the index is still the one with indisclustered set,
+			 * if needed.
 			 */
-			if (!get_index_isclustered(indexOid))
+			if ((params->options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
+				!get_index_isclustered(indexOid))
 			{
 				relation_close(OldHeap, AccessExclusiveLock);
 				pgstat_progress_end_command();
@@ -415,6 +450,9 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 		return;
 	}
 
+	Assert(OldHeap->rd_rel->relkind == RELKIND_RELATION ||
+		   OldHeap->rd_rel->relkind == RELKIND_MATVIEW);
+
 	/*
 	 * All predicate locks on the tuples or pages are about to be made
 	 * invalid, because we move tuples around.  Promote them to relation
@@ -585,8 +623,8 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
 	TransactionId frozenXid;
 	MultiXactId cutoffMulti;
 
-	/* Mark the correct index as clustered */
 	if (OidIsValid(indexOid))
+		/* Mark the correct index as clustered */
 		mark_index_clustered(OldHeap, indexOid, true);
 
 	/* Remember info about rel before closing OldHeap */
@@ -1528,8 +1566,8 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 
 			/*
 			 * Reset the relrewrite for the toast. The command-counter
-			 * increment is required here as we are about to update
-			 * the tuple that is updated as part of RenameRelationInternal.
+			 * increment is required here as we are about to update the tuple
+			 * that is updated as part of RenameRelationInternal.
 			 */
 			CommandCounterIncrement();
 			ResetRelRewrite(newrel->rd_rel->reltoastrelid);
@@ -1564,8 +1602,7 @@ get_tables_to_cluster(MemoryContext cluster_context)
 	HeapTuple	indexTuple;
 	Form_pg_index index;
 	MemoryContext old_context;
-	RelToCluster *rvtc;
-	List	   *rvs = NIL;
+	List	   *rtcs = NIL;
 
 	/*
 	 * Get all indexes that have indisclustered set and are owned by
@@ -1579,21 +1616,20 @@ get_tables_to_cluster(MemoryContext cluster_context)
 	scan = table_beginscan_catalog(indRelation, 1, &entry);
 	while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
+		RelToCluster *rtc;
+
 		index = (Form_pg_index) GETSTRUCT(indexTuple);
 
 		if (!pg_class_ownercheck(index->indrelid, GetUserId()))
 			continue;
 
-		/*
-		 * We have to build the list in a different memory context so it will
-		 * survive the cross-transaction processing
-		 */
+		/* Use a permanent memory context for the result list */
 		old_context = MemoryContextSwitchTo(cluster_context);
 
-		rvtc = (RelToCluster *) palloc(sizeof(RelToCluster));
-		rvtc->tableOid = index->indrelid;
-		rvtc->indexOid = index->indexrelid;
-		rvs = lappend(rvs, rvtc);
+		rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
+		rtc->tableOid = index->indrelid;
+		rtc->indexOid = index->indexrelid;
+		rtcs = lappend(rtcs, rtc);
 
 		MemoryContextSwitchTo(old_context);
 	}
@@ -1601,5 +1637,45 @@ get_tables_to_cluster(MemoryContext cluster_context)
 
 	relation_close(indRelation, AccessShareLock);
 
-	return rvs;
+	return rtcs;
+}
+
+/*
+ * Given an index on a partitioned table, return a list of RelToCluster for
+ * all the children leaves tables/indexes.
+ *
+ * Caller must hold lock on the table containing the index.
+ */
+static List *
+get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
+{
+	List	   *inhoids;
+	ListCell   *lc;
+	List	   *rtcs = NIL;
+	MemoryContext old_context;
+
+	/* Do not lock the children until they're processed */
+	inhoids = find_all_inheritors(indexOid, NoLock, NULL);
+
+	/* Use a permanent memory context for the result list */
+	old_context = MemoryContextSwitchTo(cluster_context);
+
+	foreach(lc, inhoids)
+	{
+		Oid			indexrelid = lfirst_oid(lc);
+		Oid			relid = IndexGetRelation(indexrelid, false);
+		RelToCluster *rtc;
+
+		/* consider only leaf indexes */
+		if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
+			continue;
+
+		rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
+		rtc->tableOid = relid;
+		rtc->indexOid = indexrelid;
+		rtcs = lappend(rtcs, rtc);
+	}
+
+	MemoryContextSwitchTo(old_context);
+	return rtcs;
 }
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 51b4a00d50..7febb5018f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -16911,11 +16911,11 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid,
 
 /*
  * This is intended as a callback for RangeVarGetRelidExtended().  It allows
- * the relation to be locked only if (1) it's a plain table, materialized
- * view, or TOAST table and (2) the current user is the owner (or the
- * superuser).  This meets the permission-checking needs of CLUSTER, REINDEX
- * TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be
- * used by all.
+ * the relation to be locked only if (1) it's a plain or partitioned table,
+ * materialized view, or TOAST table and (2) the current user is the owner (or
+ * the superuser).  This meets the permission-checking needs of CLUSTER,
+ * REINDEX TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it
+ * can be used by all.
  */
 void
 RangeVarCallbackOwnsTable(const RangeVar *relation,
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3f9dfffd57..6f040674a2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -897,6 +897,7 @@ static const SchemaQuery Query_for_list_of_clusterables = {
 	.catname = "pg_catalog.pg_class c",
 	.selcondition =
 	"c.relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+	CppAsString2(RELKIND_PARTITIONED_TABLE) ", "
 	CppAsString2(RELKIND_MATVIEW) ")",
 	.viscondition = "pg_catalog.pg_table_is_visible(c.oid)",
 	.namespace = "c.relnamespace",
diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h
index 3db375d7cc..3c279f6210 100644
--- a/src/include/commands/cluster.h
+++ b/src/include/commands/cluster.h
@@ -19,9 +19,11 @@
 #include "utils/relcache.h"
 
 
-/* flag bits for ClusterParams->flags */
-#define CLUOPT_RECHECK 0x01		/* recheck relation state */
-#define CLUOPT_VERBOSE 0x02		/* print progress info */
+/* flag bits for ClusterParams->options */
+#define CLUOPT_VERBOSE 0x01		/* print progress info */
+#define CLUOPT_RECHECK 0x02		/* recheck relation state */
+#define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for
+										 * indisclustered */
 
 /* options for CLUSTER */
 typedef struct ClusterParams
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index e46a66952f..3f2758d13f 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -444,13 +444,52 @@ DROP TABLE clustertest;
 CREATE TABLE clustertest (f1 int PRIMARY KEY);
 CLUSTER clustertest USING clustertest_pkey;
 CLUSTER clustertest;
--- Check that partitioned tables cannot be clustered
+-- Check that partitioned tables can be clustered
 CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a);
+CREATE TABLE clstrpart1 PARTITION OF clstrpart FOR VALUES FROM (1)TO(10) PARTITION BY RANGE (a);
+CREATE TABLE clstrpart11 PARTITION OF clstrpart1 FOR VALUES FROM (1)TO(10);
+CREATE TABLE clstrpart12 PARTITION OF clstrpart1 FOR VALUES FROM (10)TO(20) PARTITION BY RANGE(a);
+CREATE TABLE clstrpart2 PARTITION OF clstrpart FOR VALUES FROM (20)TO(30);
+CREATE TABLE clstrpart3 PARTITION OF clstrpart DEFAULT PARTITION BY RANGE(a);
+CREATE TABLE clstrpart33 PARTITION OF clstrpart3 DEFAULT;
+CREATE INDEX clstrpart_only_idx ON ONLY clstrpart (a);
+CLUSTER clstrpart USING clstrpart_only_idx; -- fails
+ERROR:  cannot cluster on invalid index "clstrpart_only_idx"
+DROP INDEX clstrpart_only_idx;
 CREATE INDEX clstrpart_idx ON clstrpart (a);
+-- Check that clustering sets new relfilenodes:
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+CLUSTER clstrpart USING clstrpart_idx;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+   relname   | level | relkind | ?column? 
+-------------+-------+---------+----------
+ clstrpart   |     0 | p       | t
+ clstrpart1  |     1 | p       | t
+ clstrpart11 |     2 | r       | f
+ clstrpart12 |     2 | p       | t
+ clstrpart2  |     1 | r       | f
+ clstrpart3  |     1 | p       | t
+ clstrpart33 |     2 | r       | f
+(7 rows)
+
+-- Partitioned indexes aren't and can't be marked un/clustered:
+\d clstrpart
+       Partitioned table "public.clstrpart"
+ Column |  Type   | Collation | Nullable | Default 
+--------+---------+-----------+----------+---------
+ a      | integer |           |          | 
+Partition key: RANGE (a)
+Indexes:
+    "clstrpart_idx" btree (a)
+Number of partitions: 3 (Use \d+ to list them.)
+
+CLUSTER clstrpart;
+ERROR:  there is no previously clustered index for table "clstrpart"
+ALTER TABLE clstrpart SET WITHOUT CLUSTER;
+ERROR:  cannot mark index clustered in partitioned table
 ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
 ERROR:  cannot mark index clustered in partitioned table
-CLUSTER clstrpart USING clstrpart_idx;
-ERROR:  cannot cluster a partitioned table
 DROP TABLE clstrpart;
 -- Test CLUSTER with external tuplesorting
 create table clstr_4 as select * from tenk1;
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index aee9cf83e0..74118993a8 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -202,11 +202,28 @@ CREATE TABLE clustertest (f1 int PRIMARY KEY);
 CLUSTER clustertest USING clustertest_pkey;
 CLUSTER clustertest;
 
--- Check that partitioned tables cannot be clustered
+-- Check that partitioned tables can be clustered
 CREATE TABLE clstrpart (a int) PARTITION BY RANGE (a);
+CREATE TABLE clstrpart1 PARTITION OF clstrpart FOR VALUES FROM (1)TO(10) PARTITION BY RANGE (a);
+CREATE TABLE clstrpart11 PARTITION OF clstrpart1 FOR VALUES FROM (1)TO(10);
+CREATE TABLE clstrpart12 PARTITION OF clstrpart1 FOR VALUES FROM (10)TO(20) PARTITION BY RANGE(a);
+CREATE TABLE clstrpart2 PARTITION OF clstrpart FOR VALUES FROM (20)TO(30);
+CREATE TABLE clstrpart3 PARTITION OF clstrpart DEFAULT PARTITION BY RANGE(a);
+CREATE TABLE clstrpart33 PARTITION OF clstrpart3 DEFAULT;
+CREATE INDEX clstrpart_only_idx ON ONLY clstrpart (a);
+CLUSTER clstrpart USING clstrpart_only_idx; -- fails
+DROP INDEX clstrpart_only_idx;
 CREATE INDEX clstrpart_idx ON clstrpart (a);
-ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
+-- Check that clustering sets new relfilenodes:
+CREATE TEMP TABLE old_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
 CLUSTER clstrpart USING clstrpart_idx;
+CREATE TEMP TABLE new_cluster_info AS SELECT relname, level, relfilenode, relkind FROM pg_partition_tree('clstrpart'::regclass) AS tree JOIN pg_class c ON c.oid=tree.relid ;
+SELECT relname, old.level, old.relkind, old.relfilenode = new.relfilenode FROM old_cluster_info AS old JOIN new_cluster_info AS new USING (relname) ORDER BY relname COLLATE "C";
+-- Partitioned indexes aren't and can't be marked un/clustered:
+\d clstrpart
+CLUSTER clstrpart;
+ALTER TABLE clstrpart SET WITHOUT CLUSTER;
+ALTER TABLE clstrpart CLUSTER ON clstrpart_idx;
 DROP TABLE clstrpart;
 
 -- Test CLUSTER with external tuplesorting


^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-03-31 10:54  Alvaro Herrera <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 2 replies; 29+ messages in thread

From: Alvaro Herrera @ 2022-03-31 10:54 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

I realized after posting that we used to allow clustering toast tables,
but after my changes we no longer do.  (Justin's version had a
RELKIND_HAS_STORAGE test here instead, which seemed a little too lax.) I
don't know why we allowed it and I don't know of anyone who has ever
used that feature and we don't have any test coverage for it, but I
don't have any reason to explicitly disallow it either.  So I propose to
continue to allow it:

From 05ba6124422fb7c2fd19575e905e444ba3eef1e5 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <[email protected]>
Date: Thu, 31 Mar 2022 12:49:57 +0200
Subject: [PATCH] allow to cluster toast tables

---
 src/backend/commands/cluster.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8417cbdb67..b391d7c434 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -451,7 +451,8 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 	}
 
 	Assert(OldHeap->rd_rel->relkind == RELKIND_RELATION ||
-		   OldHeap->rd_rel->relkind == RELKIND_MATVIEW);
+		   OldHeap->rd_rel->relkind == RELKIND_MATVIEW ||
+		   OldHeap->rd_rel->relkind == RELKIND_TOASTVALUE);
 
 	/*
 	 * All predicate locks on the tuples or pages are about to be made
-- 
2.30.2

-- 
Álvaro Herrera         PostgreSQL Developer  —  https://www.EnterpriseDB.com/
"Java is clearly an example of money oriented programming"  (A. Stepanov)





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-03-31 12:39  Robert Haas <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 0 replies; 29+ messages in thread

From: Robert Haas @ 2022-03-31 12:39 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; PostgreSQL Hackers <[email protected]>; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Thu, Mar 31, 2022 at 6:54 AM Alvaro Herrera <[email protected]> wrote:
> I realized after posting that we used to allow clustering toast tables,
> but after my changes we no longer do.  (Justin's version had a
> RELKIND_HAS_STORAGE test here instead, which seemed a little too lax.) I
> don't know why we allowed it and I don't know of anyone who has ever
> used that feature and we don't have any test coverage for it, but I
> don't have any reason to explicitly disallow it either.  So I propose to
> continue to allow it:

I think that's probably a good decision. It's certainly useful to have
a way to force a rewrite of a TOAST table, although a lot of people
who would benefit from that operation probably don't know that they
need it, or don't know that they need just that, and end up rewriting
both the main table and the TOAST table. Whether it's useful to be
able to run CLUSTER specifically rather than VACUUM FULL on the TOAST
table is less clear, but I don't think we're likely to save anything
by forbidding it. Maybe we should consider adding a test, though.

-- 
Robert Haas
EDB: http://www.enterprisedb.com





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-03-31 14:10  Justin Pryzby <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 0 replies; 29+ messages in thread

From: Justin Pryzby @ 2022-03-31 14:10 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Wed, Mar 30, 2022 at 10:51:43PM +0200, Alvaro Herrera wrote:
> On 2022-Feb-23, Justin Pryzby wrote:
> 
> > I hope that Alvaro will comment on the simplified patches.  If multiple people
> > think the patch isn't worth it, feel free to close it.  But I don't see how
> > complexity could be the reason.
> 
> I gave your patch a look and it seems a reasonable thing to do.  Maybe
> not terribly useful in most cases, but there may be some cases for which
> it is.  I found some part of it a bit repetitive, so I moved things
> around a bit.  What do think about this?

Thanks for looking at it.

The changes to finish_heap_swap() and get_tables_to_cluster() are superfluous,
right ?

I think this comment is worth preserving (it'd be okay if it lived in the
commit message).
-                        * Expand partitioned relations for CLUSTER (the corresponding
-                        * thing for VACUUM FULL happens in and around expand_vacuum_rel()

+       if (rel != NULL) In this case, maybe it should Assert() that it's
relkind=p (mostly for purposes of self-documentation).

+    partition of the specified partitioned index (which must be specified).
This is my own language, but now seems repetitive.  I think the parenthetical
part should be a separate sentance: "For partitioned indexes, the index may not
be omitted.".

Otherwise looks ok.

diff --git a/doc/src/sgml/ref/cluster.sgml b/doc/src/sgml/ref/cluster.sgml
index b3463ae5c46..fbc090cd0b0 100644
--- a/doc/src/sgml/ref/cluster.sgml
+++ b/doc/src/sgml/ref/cluster.sgml
@@ -199,7 +199,8 @@ CLUSTER [VERBOSE]
 
    <para>
     Clustering a partitioned table clusters each of its partitions using the
-    partition of the specified partitioned index (which must be specified).
+    partition of the specified partitioned index.  When clustering a
+    partitioned table, the index may not be omitted.
    </para>
 
  </refsect1>
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 8417cbdb67f..412147f05bc 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -231,6 +231,7 @@ cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
 	params.options |= CLUOPT_RECHECK;
 	if (rel != NULL)
 	{
+		Assert (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
 		check_index_is_clusterable(rel, indexOid, true, AccessShareLock);
 		rtcs = get_tables_to_cluster_partitioned(cluster_context, indexOid);
 
@@ -451,6 +452,7 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
 	}
 
 	Assert(OldHeap->rd_rel->relkind == RELKIND_RELATION ||
+		   OldHeap->rd_rel->relkind == RELKIND_TOASTVALUE ||
 		   OldHeap->rd_rel->relkind == RELKIND_MATVIEW);
 
 	/*
diff --git a/src/test/regress/expected/cluster.out b/src/test/regress/expected/cluster.out
index 3f2758d13f6..6cf18c8d321 100644
--- a/src/test/regress/expected/cluster.out
+++ b/src/test/regress/expected/cluster.out
@@ -305,6 +305,8 @@ WHERE pg_class.oid=indexrelid
 ---------
 (0 rows)
 
+-- Verify that toast is clusterable
+CLUSTER pg_toast.pg_toast_826 USING pg_toast_826_index;
 -- Verify that clustering all tables does in fact cluster the right ones
 CREATE USER regress_clstr_user;
 CREATE TABLE clstr_1 (a INT PRIMARY KEY);
diff --git a/src/test/regress/sql/cluster.sql b/src/test/regress/sql/cluster.sql
index 74118993a82..ae27c35f65d 100644
--- a/src/test/regress/sql/cluster.sql
+++ b/src/test/regress/sql/cluster.sql
@@ -104,6 +104,9 @@ WHERE pg_class.oid=indexrelid
 	AND pg_class_2.relname = 'clstr_tst'
 	AND indisclustered;
 
+-- Verify that toast is clusterable
+CLUSTER pg_toast.pg_toast_826 USING pg_toast_826_index;
+
 -- Verify that clustering all tables does in fact cluster the right ones
 CREATE USER regress_clstr_user;
 CREATE TABLE clstr_1 (a INT PRIMARY KEY);





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-03-31 14:11  Justin Pryzby <[email protected]>
  parent: Alvaro Herrera <[email protected]>
  1 sibling, 0 replies; 29+ messages in thread

From: Justin Pryzby @ 2022-03-31 14:11 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Michael Paquier <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Thu, Mar 31, 2022 at 12:54:36PM +0200, Alvaro Herrera wrote:
> I realized after posting that we used to allow clustering toast tables,
> but after my changes we no longer do.  (Justin's version had a
> RELKIND_HAS_STORAGE test here instead, which seemed a little too lax.) I
> don't know why we allowed it and I don't know of anyone who has ever
> used that feature and we don't have any test coverage for it, but I
> don't have any reason to explicitly disallow it either.  So I propose to
> continue to allow it:

Good catch.

My daily vacuum script would've discovered that they're no longer supported, as
it tests for (among other things) c.relkind IN ('r','t').  That clusters tables
that have an indisclustered set and vacuums various others.  (BTW, it's the
same script that discovered in 2019 that clustering on expressional indexes had
been broken by the heapam changes).

I think the response should be to add a test case, which could be 0001 or
00099.





^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-04-13 06:50  Michael Paquier <[email protected]>
  1 sibling, 1 reply; 29+ messages in thread

From: Michael Paquier @ 2022-04-13 06:50 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Mon, Apr 11, 2022 at 09:06:09AM -0500, Justin Pryzby wrote:
> On Sat, Apr 02, 2022 at 07:21:11PM +0200, Alvaro Herrera wrote:
>> 1. in VACUUM FULL we only process partitions that are owned by the
>> invoking user.  We don't have this test in the new code.  I'm not sure
>> why do we do that there; is it worth doing the same here?

I think that adding a test is a good idea for such things.  Perhaps we
could have an isolation test, but what Justin is proposing seems good
enough to me for this goal.

> That dates to a556549d7 (see also cbe24a6dd8 for an earlier commit in CLUSTER
> itself).  The reason was to avoid blocking if an unprivileged user runs VACUUM
> FULL which would try to lock things (including shared catalogs) before checking
> if they have permission to vacuum them.  That commit also initially checks the
> owner of the partitioned table, and then re-checking owner of partitions later
> on.
> 
> A similar issue exists here.  But 1) catalog tables are not partitioned, and,
> 2) ownership of a partitioned table is checked immediately.  So the problem can
> only occur if a user who owns a partitioned table but doesn't own all its
> partitions tries to cluster it, and it blocks behind another session.  Fixing
> this is probably a good idea, but seems improbable that it would avoid a DOS.

Catalogs are out of the picture as you say and I would not worry about
them becoming somewhat partitioned even in the far future.  Are you
saying that it is possible for a user kicking a CLUSTER command on a
partitioned table who has no ownership on some of the partitions to
do some blocking table_open() calls if the permission check is not
done in get_tables_to_cluster_partitioned()?  Hence, this user could
block the access to such partitions?  I am not sure that we need to
add any new ownership checks here as CLUOPT_RECHECK gets added to the
parameters in cluster() before calling cluster_multiple_rels(), then
we do a mix of try_relation_open() with a skip when we are not the
owner anymore.  So this logic looks sound to me.  In short, you don't
need this extra check, and the test proposed in 0002 keeps the same
behavior.

>> 2. We should silently skip a partition that's a foreign table, I
>> suppose.
> 
> I think it's not needed, since the loop over index children doesn't see a child
> index on the foreign table?

Hmm.  That may be a sign to add an assertion, at least, or something
based on RELKIND_HAS_STORAGE().

I was wondering what 0001 was doing here as that's a separate issue,
but it looked fine so I have applied it.

+       /* Use a permanent memory context for the result list */
+       old_context = MemoryContextSwitchTo(cluster_context);
+
        rtc = (RelToCluster *) palloc(sizeof(RelToCluster));

Independently of the extra ownership check, the memory context
manipulation has to be fixed and the code shoudl switch to
RelToCluster only when saving an item.

+CREATE ROLE ptnowner;
Roles that are created in the regression tests need to be prefixed
with "regress_", or some buildfarm members will complain.  FWIW, I
enforce -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS in all my dev
builds.

I have added an open item for now, but the whole looks
straight-forward to me.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-04-13 21:11  Michael Paquier <[email protected]>
  parent: Michael Paquier <[email protected]>
  0 siblings, 0 replies; 29+ messages in thread

From: Michael Paquier @ 2022-04-13 21:11 UTC (permalink / raw)
  To: Justin Pryzby <[email protected]>; +Cc: Alvaro Herrera <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Wed, Apr 13, 2022 at 05:52:14AM -0500, Justin Pryzby wrote:
> Are you sure?  The ownership re-check in cluster_rel() occurs after acquiring
> locks.

Yep, you are right.  However, the SQL test does not check for this
blocking scenario.  In short, removing the new ACL check in
get_tables_to_cluster_partitioned() makes the test behave the same
way.  Could you implement an isolation check to make sure that the
difference is visible?  The SQL check looks useful in itself, either
way.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-04-16 11:58  Michael Paquier <[email protected]>
  1 sibling, 1 reply; 29+ messages in thread

From: Michael Paquier @ 2022-04-16 11:58 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Thu, Apr 14, 2022 at 10:37:06PM +0200, Alvaro Herrera wrote:
> Thanks for the patch -- I have pushed it now, with some wording changes
> and renaming the role to regress_* to avoid buildfarm's ire.

Cool, thanks.

> Michaël in addition proposes an isolation test.  I'm not sure; is it
> worth the additional test run time?  It doesn't seem a critical issue.
> But if anybody feels like contributing one, step right ahead.

Well, I am a bit annoyed that we don't actually check that a CLUSTER
command does not block when doing a CLUSTER on a partitioned table
while a lock is held on one of its partitions.  So, attached is a
proposal of patch to improve the test coverage in this area.  While on
it, I have added a test with a normal table.  You can see the
difference once you remove the ACL check added recently in
get_tables_to_cluster_partitioned().  What do you think?
--
Michael


Attachments:

  [text/x-diff] 0001-Add-isolation-tests-for-CLUSTER-with-partitions.patch (7.0K, ../../[email protected]/2-0001-Add-isolation-tests-for-CLUSTER-with-partitions.patch)
  download | inline diff:
From fdf5347bf4853f19341a8d67a655cae9fece15f0 Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Sat, 16 Apr 2022 20:52:03 +0900
Subject: [PATCH] Add isolation tests for CLUSTER with partitions

---
 .../expected/cluster-conflict-partition.out   | 35 ++++++++++++++++++
 .../isolation/expected/cluster-conflict.out   | 19 ++++++++++
 src/test/isolation/isolation_schedule         |  2 +
 .../specs/cluster-conflict-partition.spec     | 37 +++++++++++++++++++
 .../isolation/specs/cluster-conflict.spec     | 30 +++++++++++++++
 5 files changed, 123 insertions(+)
 create mode 100644 src/test/isolation/expected/cluster-conflict-partition.out
 create mode 100644 src/test/isolation/expected/cluster-conflict.out
 create mode 100644 src/test/isolation/specs/cluster-conflict-partition.spec
 create mode 100644 src/test/isolation/specs/cluster-conflict.spec

diff --git a/src/test/isolation/expected/cluster-conflict-partition.out b/src/test/isolation/expected/cluster-conflict-partition.out
new file mode 100644
index 0000000000..7acb675c97
--- /dev/null
+++ b/src/test/isolation/expected/cluster-conflict-partition.out
@@ -0,0 +1,35 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_lock_parent s2_auth s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_cluster_part;
+step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
+step s1_commit: COMMIT;
+step s2_cluster: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_lock_parent s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_cluster_part;
+step s1_lock_parent: LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind; <waiting ...>
+step s1_commit: COMMIT;
+step s2_cluster: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s1_lock_child s2_auth s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_cluster_part;
+step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_lock_child s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_cluster_part;
+step s1_lock_child: LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_cluster: CLUSTER cluster_part_tab USING cluster_part_ind;
+step s1_commit: COMMIT;
+step s2_reset: RESET ROLE;
diff --git a/src/test/isolation/expected/cluster-conflict.out b/src/test/isolation/expected/cluster-conflict.out
new file mode 100644
index 0000000000..614d8f9d15
--- /dev/null
+++ b/src/test/isolation/expected/cluster-conflict.out
@@ -0,0 +1,19 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_lock s2_auth s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s1_lock: LOCK cluster_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_auth: SET ROLE regress_cluster_conflict;
+step s2_cluster: CLUSTER cluster_tab USING cluster_ind; <waiting ...>
+step s1_commit: COMMIT;
+step s2_cluster: <... completed>
+step s2_reset: RESET ROLE;
+
+starting permutation: s1_begin s2_auth s1_lock s2_cluster s1_commit s2_reset
+step s1_begin: BEGIN;
+step s2_auth: SET ROLE regress_cluster_conflict;
+step s1_lock: LOCK cluster_tab IN SHARE UPDATE EXCLUSIVE MODE;
+step s2_cluster: CLUSTER cluster_tab USING cluster_ind; <waiting ...>
+step s1_commit: COMMIT;
+step s2_cluster: <... completed>
+step s2_reset: RESET ROLE;
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 607760386e..529a4cbd4d 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -102,6 +102,8 @@ test: partition-key-update-2
 test: partition-key-update-3
 test: partition-key-update-4
 test: plpgsql-toast
+test: cluster-conflict
+test: cluster-conflict-partition
 test: truncate-conflict
 test: serializable-parallel
 test: serializable-parallel-2
diff --git a/src/test/isolation/specs/cluster-conflict-partition.spec b/src/test/isolation/specs/cluster-conflict-partition.spec
new file mode 100644
index 0000000000..5091f684a9
--- /dev/null
+++ b/src/test/isolation/specs/cluster-conflict-partition.spec
@@ -0,0 +1,37 @@
+# Tests for locking conflicts with CLUSTER command and partitions.
+
+setup
+{
+	CREATE ROLE regress_cluster_part;
+	CREATE TABLE cluster_part_tab (a int) PARTITION BY LIST (a);
+	CREATE TABLE cluster_part_tab1 PARTITION OF cluster_part_tab FOR VALUES IN (1);
+	CREATE TABLE cluster_part_tab2 PARTITION OF cluster_part_tab FOR VALUES IN (2);
+	CREATE INDEX cluster_part_ind ON cluster_part_tab(a);
+	ALTER TABLE cluster_part_tab OWNER TO regress_cluster_part;
+}
+
+teardown
+{
+	DROP TABLE cluster_part_tab;
+	DROP ROLE regress_cluster_part;
+}
+
+session s1
+step s1_begin          { BEGIN; }
+step s1_lock_parent    { LOCK cluster_part_tab IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1_lock_child     { LOCK cluster_part_tab1 IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1_commit         { COMMIT; }
+
+session s2
+step s2_auth           { SET ROLE regress_cluster_part; }
+step s2_cluster        { CLUSTER cluster_part_tab USING cluster_part_ind; }
+step s2_reset          { RESET ROLE; }
+
+# CLUSTER on the parent waits if locked, passes for all cases.
+permutation s1_begin s1_lock_parent s2_auth s2_cluster s1_commit s2_reset
+permutation s1_begin s2_auth s1_lock_parent s2_cluster s1_commit s2_reset
+
+# When taking a lock on a partition leaf, CLUSTER on the parent skips
+# the leaf, passes for all cases.
+permutation s1_begin s1_lock_child s2_auth s2_cluster s1_commit s2_reset
+permutation s1_begin s2_auth s1_lock_child s2_cluster s1_commit s2_reset
diff --git a/src/test/isolation/specs/cluster-conflict.spec b/src/test/isolation/specs/cluster-conflict.spec
new file mode 100644
index 0000000000..2e1d547f01
--- /dev/null
+++ b/src/test/isolation/specs/cluster-conflict.spec
@@ -0,0 +1,30 @@
+# Tests for locking conflicts with CLUSTER command.
+
+setup
+{
+	CREATE ROLE regress_cluster_conflict;
+	CREATE TABLE cluster_tab (a int);
+	CREATE INDEX cluster_ind ON cluster_tab(a);
+	ALTER TABLE cluster_tab OWNER TO regress_cluster_conflict;
+}
+
+teardown
+{
+	DROP TABLE cluster_tab;
+	DROP ROLE regress_cluster_conflict;
+}
+
+session s1
+step s1_begin          { BEGIN; }
+step s1_lock           { LOCK cluster_tab IN SHARE UPDATE EXCLUSIVE MODE; }
+step s1_commit         { COMMIT; }
+
+session s2
+step s2_auth           { SET ROLE regress_cluster_conflict; }
+step s2_cluster        { CLUSTER cluster_tab USING cluster_ind; }
+step s2_reset          { RESET ROLE; }
+
+# The role has privileges to cluster the table, CLUSTER will block if
+# another session holds a lock on the table and succeed in all cases.
+permutation s1_begin s1_lock s2_auth s2_cluster s1_commit s2_reset
+permutation s1_begin s2_auth s1_lock s2_cluster s1_commit s2_reset
-- 
2.35.2



  [application/pgp-signature] signature.asc (833B, ../../[email protected]/3-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread

* Re: CLUSTER on partitioned index
@ 2022-04-26 05:17  Michael Paquier <[email protected]>
  parent: Michael Paquier <[email protected]>
  0 siblings, 0 replies; 29+ messages in thread

From: Michael Paquier @ 2022-04-26 05:17 UTC (permalink / raw)
  To: Alvaro Herrera <[email protected]>; +Cc: Justin Pryzby <[email protected]>; 李杰(慎追) <[email protected]>; [email protected]; 曾文旌(义从) <[email protected]>; Zhihong Yu <[email protected]>

On Sat, Apr 16, 2022 at 08:58:50PM +0900, Michael Paquier wrote:
> Well, I am a bit annoyed that we don't actually check that a CLUSTER
> command does not block when doing a CLUSTER on a partitioned table
> while a lock is held on one of its partitions.  So, attached is a
> proposal of patch to improve the test coverage in this area.  While on
> it, I have added a test with a normal table.  You can see the
> difference once you remove the ACL check added recently in
> get_tables_to_cluster_partitioned().  What do you think?

This was the last reason why this was listed as an open item, so,
hearing nothing, I have applied this patch to add those extra tests,
and switched the item as fixed.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../YmeAV6gG%[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 29+ messages in thread


end of thread, other threads:[~2022-04-26 05:17 UTC | newest]

Thread overview: 29+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-10-28 00:33 CLUSTER on partitioned index Justin Pryzby <[email protected]>
2020-11-05 02:23 ` Justin Pryzby <[email protected]>
2020-11-16 01:53   ` Justin Pryzby <[email protected]>
2020-11-29 02:03     ` Justin Pryzby <[email protected]>
2021-01-18 18:34       ` Justin Pryzby <[email protected]>
2021-02-06 14:45         ` Justin Pryzby <[email protected]>
2021-02-06 17:21           ` Zhihong Yu <[email protected]>
2021-02-10 20:04           ` Justin Pryzby <[email protected]>
2021-03-07 04:13             ` Justin Pryzby <[email protected]>
2021-04-02 20:03               ` Justin Pryzby <[email protected]>
2021-04-03 16:14                 ` Zhihong Yu <[email protected]>
2021-07-21 00:27                 ` Alvaro Herrera <[email protected]>
2021-07-21 00:34                   ` Michael Paquier <[email protected]>
2021-07-21 11:01                   ` Laurenz Albe <[email protected]>
2021-09-12 20:10                   ` Justin Pryzby <[email protected]>
2021-09-23 18:18                     ` Matthias van de Meent <[email protected]>
2021-09-23 23:56                       ` Justin Pryzby <[email protected]>
2021-12-03 01:16                         ` Michael Paquier <[email protected]>
2022-02-23 18:47                   ` Justin Pryzby <[email protected]>
2022-03-30 20:51                     ` Alvaro Herrera <[email protected]>
2022-03-31 10:54                       ` Alvaro Herrera <[email protected]>
2022-03-31 12:39                         ` Robert Haas <[email protected]>
2022-03-31 14:11                         ` Justin Pryzby <[email protected]>
2022-03-31 14:10                       ` Justin Pryzby <[email protected]>
2021-06-06 07:28 [PATCH v1] Fix SQL-standard body empty statements handling. Julien Rouhaud <[email protected]>
2022-04-13 06:50 ` Michael Paquier <[email protected]>
2022-04-13 21:11   ` Michael Paquier <[email protected]>
2022-04-16 11:58 ` Michael Paquier <[email protected]>
2022-04-26 05:17   ` Michael Paquier <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox