New version attached. The following is implemented:
authorBruce Momjian <bruce@momjian.us>
Fri, 15 Nov 2002 03:09:39 +0000 (03:09 +0000)
committerBruce Momjian <bruce@momjian.us>
Fri, 15 Nov 2002 03:09:39 +0000 (03:09 +0000)
- CLUSTER ALL clusters all the tables that have some index with
  indisclustered set and the calling user owns.
- CLUSTER tablename clusters the named table, using the index with
  indisclustered set.  If no index has the bit set, throws elog(ERROR).
- The multi-relation version (CLUSTER ALL) uses a multitransaction
  approach, similar to what VACUUM does.

Alvaro Herrera

doc/src/sgml/ref/cluster.sgml
src/backend/commands/cluster.c
src/backend/parser/gram.y
src/backend/tcop/utility.c
src/include/commands/cluster.h
src/include/nodes/parsenodes.h
src/test/regress/expected/cluster.out
src/test/regress/sql/cluster.sql

index e7aaa5228117c51c21b152e852091ea927edbc89..a49d4d7d3965d4df184e5146a6e8ba141fe44afe 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/cluster.sgml,v 1.20 2002/09/21 18:32:54 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/cluster.sgml,v 1.21 2002/11/15 03:09:35 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -22,6 +22,8 @@ PostgreSQL documentation
   </refsynopsisdivinfo>
   <synopsis>
 CLUSTER <replaceable class="PARAMETER">indexname</replaceable> ON <replaceable class="PARAMETER">tablename</replaceable>
+CLUSTER <replaceable class="PARAMETER">tablename</replaceable>
+CLUSTER ALL
   </synopsis>
 
   <refsect2 id="R2-SQL-CLUSTER-1">
@@ -104,6 +106,20 @@ CLUSTER
    periodically re-cluster by issuing the command again.
   </para>
 
+  <para>
+   When a table is clustered, <productname>PostgreSQL</productname>
+   remembers on which index it was clustered.  In calls to
+   <command>CLUSTER <replaceable class="parameter">tablename</replaceable></command>,
+   the table is clustered on the same index that it was clustered before.
+  </para>
+
+  <para>
+   In calls to <command>CLUSTER ALL</command>, all the tables in the database
+   that the calling user owns are clustered using the saved information.  This
+   form of <command>CLUSTER</command> cannot be called from inside a
+   transaction or function.
+  </para>
+
   <refsect2 id="R2-SQL-CLUSTER-3">
    <refsect2info>
     <date>1998-09-08</date>
@@ -141,8 +157,15 @@ CLUSTER
    </para>
 
    <para>
-    CLUSTER preserves GRANT, inheritance, index, foreign key, and other
-    ancillary information about the table.
+   <command>CLUSTER</command> preserves GRANT, inheritance, index, foreign
+   key, and other ancillary information about the table.
+   </para>
+
+   <para>
+   Because <command>CLUSTER</command> remembers the clustering information,
+   one can cluster the tables one wants clustered manually the first time, and
+   setup a timed event similar to <command>VACUUM</command> so that the tables
+   are periodically and automatically clustered.
    </para>
 
    <para>
@@ -192,6 +215,18 @@ SELECT <replaceable class="parameter">columnlist</replaceable> INTO TABLE <repla
   <programlisting>
 CLUSTER emp_ind ON emp;
   </programlisting>
+  <para>
+   Cluster the employees relation using the same index that was used before:
+  </para>
+  <programlisting>
+CLUSTER emp;
+  </programlisting>
+  <para>
+   Cluster all the tables on the database that have previously been clustered:
+  </para>
+  <programlisting>
+CLUSTER ALL;
+  </programlisting>
  </refsect1>
 
  <refsect1 id="R1-SQL-CLUSTER-3">
index 48cc81ea841f25b0889e5ee5fb70e266911a70ba..db78a179b693faa68ae8ea2163894eee5d1da586 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.93 2002/11/11 22:19:21 tgl Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/commands/cluster.c,v 1.94 2002/11/15 03:09:35 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "catalog/index.h"
 #include "catalog/indexing.h"
 #include "catalog/catname.h"
+#include "catalog/namespace.h"
 #include "commands/cluster.h"
 #include "commands/tablecmds.h"
 #include "miscadmin.h"
+#include "utils/acl.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
@@ -48,12 +50,27 @@ typedef struct
    bool        isclustered;
 } IndexAttrs;
 
+/* This struct is used to pass around the information on tables to be
+ * clustered. We need this so we can make a list of them when invoked without
+ * a specific table/index pair.
+ */
+typedef struct
+{
+   Oid     tableOid;
+   Oid     indexOid;
+   bool    isPrevious;
+} relToCluster;
+
 static Oid make_new_heap(Oid OIDOldHeap, const char *NewName);
 static void copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex);
 static List *get_indexattr_list(Relation OldHeap, Oid OldIndex);
 static void recreate_indexattr(Oid OIDOldHeap, List *indexes);
 static void swap_relfilenodes(Oid r1, Oid r2);
+static void cluster_rel(relToCluster *rv);
+static bool check_cluster_ownership(Oid relOid);
+static List *get_tables_to_cluster(Oid owner);
 
+static MemoryContext cluster_context = NULL;
 
 /*
  * cluster
@@ -69,43 +86,70 @@ static void swap_relfilenodes(Oid r1, Oid r2);
  * the new table, it's better to create the indexes afterwards than to fill
  * them incrementally while we load the table.
  *
- * Permissions checks were done already.
+ * Since we may open a new transaction for each relation, we have to
+ * check that the relation still is what we think it is.
  */
 void
-cluster(RangeVar *oldrelation, char *oldindexname)
+cluster_rel(relToCluster *rvtc)
 {
-   Oid         OIDOldHeap,
-               OIDOldIndex,
-               OIDNewHeap;
+   Oid         OIDNewHeap;
    Relation    OldHeap,
                OldIndex;
    char        NewHeapName[NAMEDATALEN];
    ObjectAddress object;
    List       *indexes;
 
+   /* Check for user-requested abort. */
+   CHECK_FOR_INTERRUPTS();
+
+   /* Check if the relation and index still exist before opening them
+    */
+   if (!SearchSysCacheExists(RELOID,
+                             ObjectIdGetDatum(rvtc->tableOid),
+                             0, 0, 0) ||
+           !SearchSysCacheExists(RELOID,
+                                 ObjectIdGetDatum(rvtc->indexOid),
+                                 0, 0, 0))
+       return;
+
+   /* Check that the user still owns the relation */
+   if (!check_cluster_ownership(rvtc->tableOid))
+       return;
+
+   /* Check that the index is still the one with indisclustered set.
+    * If this is a standalone cluster, skip this test.
+    */
+   if (rvtc->isPrevious)
+   {
+       HeapTuple       tuple;
+       Form_pg_index   indexForm;
+
+       tuple = SearchSysCache(INDEXRELID,
+                              ObjectIdGetDatum(rvtc->indexOid),
+                              0, 0, 0);
+       indexForm = (Form_pg_index) GETSTRUCT(tuple);
+       if (!indexForm->indisclustered)
+       {
+           ReleaseSysCache(tuple);
+           return;
+       }
+       ReleaseSysCache(tuple);
+   }
+
    /*
     * We grab exclusive access to the target rel and index for the
     * duration of the transaction.
     */
-   OldHeap = heap_openrv(oldrelation, AccessExclusiveLock);
-   OIDOldHeap = RelationGetRelid(OldHeap);
+   OldHeap = heap_open(rvtc->tableOid, AccessExclusiveLock);
 
-   /*
-    * The index is expected to be in the same namespace as the relation.
-    */
-   OIDOldIndex = get_relname_relid(oldindexname,
-                                   RelationGetNamespace(OldHeap));
-   if (!OidIsValid(OIDOldIndex))
-       elog(ERROR, "CLUSTER: cannot find index \"%s\" for table \"%s\"",
-            oldindexname, RelationGetRelationName(OldHeap));
-   OldIndex = index_open(OIDOldIndex);
+   OldIndex = index_open(rvtc->indexOid);
    LockRelation(OldIndex, AccessExclusiveLock);
 
    /*
     * Check that index is in fact an index on the given relation
     */
    if (OldIndex->rd_index == NULL ||
-       OldIndex->rd_index->indrelid != OIDOldHeap)
+       OldIndex->rd_index->indrelid != rvtc->tableOid)
        elog(ERROR, "CLUSTER: \"%s\" is not an index for table \"%s\"",
             RelationGetRelationName(OldIndex),
             RelationGetRelationName(OldHeap));
@@ -122,7 +166,7 @@ cluster(RangeVar *oldrelation, char *oldindexname)
             RelationGetRelationName(OldHeap));
 
    /* Save the information of all indexes on the relation. */
-   indexes = get_indexattr_list(OldHeap, OIDOldIndex);
+   indexes = get_indexattr_list(OldHeap, rvtc->indexOid);
 
    /* Drop relcache refcnts, but do NOT give up the locks */
    index_close(OldIndex);
@@ -136,9 +180,9 @@ cluster(RangeVar *oldrelation, char *oldindexname)
     * namespace from the old, or we will have problems with the TEMP
     * status of temp tables.
     */
-   snprintf(NewHeapName, NAMEDATALEN, "pg_temp_%u", OIDOldHeap);
+   snprintf(NewHeapName, NAMEDATALEN, "pg_temp_%u", rvtc->tableOid);
 
-   OIDNewHeap = make_new_heap(OIDOldHeap, NewHeapName);
+   OIDNewHeap = make_new_heap(rvtc->tableOid, NewHeapName);
 
    /*
     * We don't need CommandCounterIncrement() because make_new_heap did
@@ -148,13 +192,13 @@ cluster(RangeVar *oldrelation, char *oldindexname)
    /*
     * Copy the heap data into the new table in the desired order.
     */
-   copy_heap_data(OIDNewHeap, OIDOldHeap, OIDOldIndex);
+   copy_heap_data(OIDNewHeap, rvtc->tableOid, rvtc->indexOid);
 
    /* To make the new heap's data visible (probably not needed?). */
    CommandCounterIncrement();
 
    /* Swap the relfilenodes of the old and new heaps. */
-   swap_relfilenodes(OIDOldHeap, OIDNewHeap);
+   swap_relfilenodes(rvtc->tableOid, OIDNewHeap);
 
    CommandCounterIncrement();
 
@@ -175,7 +219,7 @@ cluster(RangeVar *oldrelation, char *oldindexname)
     * Recreate each index on the relation.  We do not need
     * CommandCounterIncrement() because recreate_indexattr does it.
     */
-   recreate_indexattr(OIDOldHeap, indexes);
+   recreate_indexattr(rvtc->tableOid, indexes);
 }
 
 /*
@@ -571,3 +615,236 @@ swap_relfilenodes(Oid r1, Oid r2)
 
    heap_close(relRelation, RowExclusiveLock);
 }
+
+/*---------------------------------------------------------------------------
+ * This cluster code allows for clustering multiple tables at once.    Because
+ * of this, we cannot just run everything on a single transaction, or we
+ * would be forced to acquire exclusive locks on all the tables being
+ * clustered.  To solve this we follow a similar strategy to VACUUM code,
+ * clustering each relation in a separate transaction. For this to work,
+ * we need to:
+ *  - provide a separate memory context so that we can pass information in
+ *    a way that trascends transactions
+ *  - start a new transaction every time a new relation is clustered
+ *  - check for validity of the information on to-be-clustered relations,
+ *    as someone might have deleted a relation behind our back, or
+ *    clustered one on a different index
+ *  - end the transaction
+ *
+ * The single relation code does not have any overhead.
+ *
+ * We also allow a relation being specified without index.  In that case,
+ * the indisclustered bit will be looked up, and an ERROR will be thrown
+ * if there is no index with the bit set.
+ *---------------------------------------------------------------------------
+ */
+void
+cluster(ClusterStmt *stmt)
+{
+
+   /* This is the single relation case. */
+   if (stmt->relation != NULL)
+   {
+       Oid             indexOid = InvalidOid,
+                       tableOid;
+       relToCluster    rvtc;
+       HeapTuple       tuple;
+       Form_pg_class   classForm;
+
+       tableOid = RangeVarGetRelid(stmt->relation, false);
+       if (!check_cluster_ownership(tableOid))
+           elog(ERROR, "CLUSTER: You do not own relation %s",
+                   stmt->relation->relname);
+
+       tuple = SearchSysCache(RELOID,
+                              ObjectIdGetDatum(tableOid),
+                              0, 0, 0);
+       if (!HeapTupleIsValid(tuple))
+           elog(ERROR, "Cache lookup failed for relation %u", tableOid);
+       classForm = (Form_pg_class) GETSTRUCT(tuple);
+
+       if (stmt->indexname == NULL)
+       {
+           List       *index;
+           Relation    rel = RelationIdGetRelation(tableOid);
+           HeapTuple   ituple = NULL,
+                       idxtuple = NULL;
+
+           /* We need to fetch the index that has indisclustered set. */
+           foreach (index, RelationGetIndexList(rel))
+           {
+               Form_pg_index   indexForm;
+
+               indexOid = lfirsti(index);
+               ituple = SearchSysCache(RELOID,
+                                      ObjectIdGetDatum(indexOid),
+                                      0, 0, 0);
+               if (!HeapTupleIsValid(ituple))
+                   elog(ERROR, "Cache lookup failed for relation %u", indexOid);
+               idxtuple = SearchSysCache(INDEXRELID,
+                                         ObjectIdGetDatum(HeapTupleGetOid(ituple)),
+                                         0, 0, 0);
+               if (!HeapTupleIsValid(idxtuple))
+                   elog(ERROR, "Cache lookup failed for index %u", HeapTupleGetOid(ituple));
+               indexForm = (Form_pg_index) GETSTRUCT(idxtuple);
+               if (indexForm->indisclustered)
+                   break;
+               indexOid = InvalidOid;
+           }
+           if (indexOid == InvalidOid)
+               elog(ERROR, "CLUSTER: No previously clustered index found on table %s",
+                       stmt->relation->relname);
+           RelationClose(rel);
+           ReleaseSysCache(ituple);
+           ReleaseSysCache(idxtuple);
+       }
+       else
+       {
+           /* The index is expected to be in the same namespace as the relation. */
+           indexOid = get_relname_relid(stmt->indexname, classForm->relnamespace);
+       }
+       ReleaseSysCache(tuple);
+
+       /* XXX Maybe the namespace should be reported as well */
+       if (!OidIsValid(indexOid))
+           elog(ERROR, "CLUSTER: cannot find index \"%s\" for table \"%s\"",
+                   stmt->indexname, stmt->relation->relname);
+       rvtc.tableOid = tableOid;
+       rvtc.indexOid = indexOid;
+       rvtc.isPrevious = false;
+
+       /* Do the job */
+       cluster_rel(&rvtc);
+   }
+   else
+   {
+       /*
+        * This is the "no relation" case. We need to cluster all tables
+        * that have some index with indisclustered set.
+        */
+
+       relToCluster    *rvtc;
+       List            *rv,
+                       *rvs;
+
+       /*
+        * We cannot run CLUSTER ALL inside a user transaction block; if we were inside
+        * a transaction, then our commit- and start-transaction-command calls
+        * would not have the intended effect!
+        */
+       if (IsTransactionBlock())
+           elog(ERROR, "CLUSTER cannot run inside a BEGIN/END block");
+
+       /* Running CLUSTER from a function would free the function context */
+       if (!MemoryContextContains(QueryContext, stmt))
+           elog(ERROR, "CLUSTER cannot be called from a function");
+       /*
+        * Create special memory context for cross-transaction storage.
+        *
+        * Since it is a child of QueryContext, it will go away even in case
+        * of error.
+        */
+       cluster_context = AllocSetContextCreate(QueryContext,
+               "Cluster",
+               ALLOCSET_DEFAULT_MINSIZE,
+               ALLOCSET_DEFAULT_INITSIZE,
+               ALLOCSET_DEFAULT_MAXSIZE);
+
+       /*
+        * Build the list of relations to cluster.  Note that this lives in
+        * cluster_context.
+        */
+       rvs = get_tables_to_cluster(GetUserId());
+
+       /* Ok, now that we've got them all, cluster them one by one */
+       foreach (rv, rvs)
+       {
+           rvtc = (relToCluster *)lfirst(rv);
+
+           /* Start a new transaction for this relation. */
+           StartTransactionCommand(true);
+           cluster_rel(rvtc);
+           CommitTransactionCommand(true);
+       }
+   }
+
+   /* Start a new transaction for the cleanup work. */
+   StartTransactionCommand(true);
+
+   /* Clean up working storage */
+   if (stmt->relation == NULL)
+   {
+       MemoryContextDelete(cluster_context);
+       cluster_context = NULL;
+   }
+}
+
+/* Checks if the user owns the relation. Superusers
+ * are allowed to cluster any table.
+ */
+bool
+check_cluster_ownership(Oid relOid)
+{
+   /* Superusers bypass this check */
+   return pg_class_ownercheck(relOid, GetUserId());
+}
+
+/* Get a list of tables that the current user owns and
+ * have indisclustered set.  Return the list in a List * of rvsToCluster
+ * with the tableOid and the indexOid on which the table is already 
+ * clustered.
+ */
+List *
+get_tables_to_cluster(Oid owner)
+{
+   Relation        indRelation;
+   HeapScanDesc    scan;
+   ScanKeyData     entry;
+   HeapTuple       indexTuple;
+   Form_pg_index   index;
+   relToCluster   *rvtc;
+   List           *rvs = NIL;
+
+   /*
+    * Get all indexes that have indisclustered set.    System
+    * relations or nailed-in relations cannot ever have
+    * indisclustered set, because CLUSTER will refuse to
+    * set it when called with one of them as argument.
+    */
+   indRelation = relation_openr(IndexRelationName, RowExclusiveLock);
+   ScanKeyEntryInitialize(&entry, 0, Anum_pg_index_indisclustered,
+                          F_BOOLEQ, true);
+   scan = heap_beginscan(indRelation, SnapshotNow, 1, &entry);
+   while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
+   {
+       MemoryContext   old_context = NULL;
+
+       index = (Form_pg_index) GETSTRUCT(indexTuple);
+       if (!check_cluster_ownership(index->indrelid))
+           continue;
+
+       /*
+        * We have to build the struct in a different memory context so
+        * it will survive the cross-transaction processing
+        */
+
+       old_context = MemoryContextSwitchTo(cluster_context);
+
+       rvtc = (relToCluster *)palloc(sizeof(relToCluster));
+       rvtc->indexOid = index->indexrelid;
+       rvtc->tableOid = index->indrelid;
+       rvtc->isPrevious = true;
+       rvs = lcons((void *)rvtc, rvs);
+
+       MemoryContextSwitchTo(old_context);
+   }
+   heap_endscan(scan);
+
+   /*
+    * Release the lock on pg_index. We will check the indexes
+    * later again.
+    *
+    */
+   relation_close(indRelation, RowExclusiveLock);
+   return rvs;
+}
index b3ca71013c99cf0ce2dab4bceeb556d2c8d88c07..2eef0c049043cd7560a71efbb2809bc4b8cf1d0c 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.378 2002/11/15 02:50:08 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.379 2002/11/15 03:09:35 momjian Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -3761,6 +3761,8 @@ CreateConversionStmt:
  *
  *     QUERY:
  *             cluster <index_name> on <qualified_name>
+ *             cluster <qualified_name>
+ *             cluster ALL
  *
  *****************************************************************************/
 
@@ -3772,6 +3774,20 @@ ClusterStmt:
                   n->indexname = $2;
                   $$ = (Node*)n;
                }
+           | CLUSTER qualified_name
+               {
+                  ClusterStmt *n = makeNode(ClusterStmt);
+                  n->relation = $2;
+                  n->indexname = NULL;
+                  $$ = (Node*)n;
+               }
+           | CLUSTER ALL
+               {
+                  ClusterStmt *n = makeNode(ClusterStmt);
+                  n->relation = NULL;
+                  n->indexname = NULL;
+                  $$ = (Node*)n;
+               }
        ;
 
 /*****************************************************************************
index ddd5c7626a6c28fba221a36a0efd2f4c4f0588ea..493dbd7587a51a001718aeec872a9b208a1a4b0a 100644 (file)
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.181 2002/11/13 00:44:09 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/tcop/utility.c,v 1.182 2002/11/15 03:09:38 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -186,7 +186,6 @@ ProcessUtility(Node *parsetree,
               CommandDest dest,
               char *completionTag)
 {
-   char       *relname;
 
    if (completionTag)
        completionTag[0] = '\0';
@@ -702,9 +701,7 @@ ProcessUtility(Node *parsetree,
            {
                ClusterStmt *stmt = (ClusterStmt *) parsetree;
 
-               CheckOwnership(stmt->relation, true);
-
-               cluster(stmt->relation, stmt->indexname);
+               cluster(stmt);
            }
            break;
 
@@ -833,8 +830,8 @@ ProcessUtility(Node *parsetree,
 
                switch (stmt->reindexType)
                {
+                   char    *relname;
                    case INDEX:
-                       relname = (char *) stmt->relation->relname;
                        CheckOwnership(stmt->relation, false);
                        ReindexIndex(stmt->relation, stmt->force);
                        break;
index ff3a8d82d39185c6b44ac677e702c5ae8261d52f..2490278b6ca562d0a074aadd0f6062b7a8817817 100644 (file)
@@ -6,16 +6,17 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $Id: cluster.h,v 1.15 2002/08/10 21:00:34 momjian Exp $
+ * $Id: cluster.h,v 1.16 2002/11/15 03:09:39 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef CLUSTER_H
 #define CLUSTER_H
 
+#include <nodes/parsenodes.h>
 /*
  * functions
  */
-extern void cluster(RangeVar *oldrelation, char *oldindexname);
+extern void cluster(ClusterStmt *stmt);
 
 #endif   /* CLUSTER_H */
index 1198a81de5e6fbb4cfc17d6dedea4649a4487ac8..cd976cd1a14ca5c6c1e01731148cc17617094c8d 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.214 2002/11/15 02:50:12 momjian Exp $
+ * $Id: parsenodes.h,v 1.215 2002/11/15 03:09:39 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1533,7 +1533,7 @@ typedef struct DropdbStmt
 typedef struct ClusterStmt
 {
    NodeTag     type;
-   RangeVar   *relation;       /* relation being indexed */
+   RangeVar   *relation;       /* relation being indexed, or NULL if all */
    char       *indexname;      /* original index defined */
 } ClusterStmt;
 
index 6a2ba61e8327ef31e7ae07f26e0346443368cee8..ee39c3a8482e0ffa47db040cc069f82adb5882c3 100644 (file)
@@ -285,3 +285,67 @@ WHERE pg_class.oid=indexrelid
  clstr_tst_c
 (1 row)
 
+-- Verify that clustering all tables does in fact cluster the right ones
+CREATE USER clstr_user;
+CREATE TABLE clstr_1 (a INT PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 'clstr_1_pkey' for table 'clstr_1'
+CREATE TABLE clstr_2 (a INT PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 'clstr_2_pkey' for table 'clstr_2'
+CREATE TABLE clstr_3 (a INT PRIMARY KEY);
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index 'clstr_3_pkey' for table 'clstr_3'
+ALTER TABLE clstr_1 OWNER TO clstr_user;
+ALTER TABLE clstr_3 OWNER TO clstr_user;
+GRANT SELECT ON clstr_2 TO clstr_user;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+CLUSTER clstr_1_pkey ON clstr_1;
+CLUSTER clstr_2_pkey ON clstr_2;
+SELECT * FROM clstr_1 UNION ALL
+  SELECT * FROM clstr_2 UNION ALL
+  SELECT * FROM clstr_3;
+ a 
+---
+ 1
+ 2
+ 1
+ 2
+ 2
+ 1
+(6 rows)
+
+-- revert to the original state
+DELETE FROM clstr_1;
+DELETE FROM clstr_2;
+DELETE FROM clstr_3;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+-- this user can only cluster clstr_1 and clstr_3, but the latter
+-- has not been clustered
+SET SESSION AUTHORIZATION clstr_user;
+CLUSTER ALL;
+SELECT * FROM clstr_1 UNION ALL
+  SELECT * FROM clstr_2 UNION ALL
+  SELECT * FROM clstr_3;
+ a 
+---
+ 1
+ 2
+ 2
+ 1
+ 2
+ 1
+(6 rows)
+
+-- clean up
+\c -
+DROP TABLE clstr_1;
+DROP TABLE clstr_3;
+DROP USER clstr_user;
index 384a185d09e91049c43bdf6aea5506727d9fbcea..dc05f9f363573dbf9bd223ddbb68c0b11509dacd 100644 (file)
@@ -86,3 +86,59 @@ WHERE pg_class.oid=indexrelid
    AND indrelid=pg_class_2.oid
    AND pg_class_2.relname = 'clstr_tst'
    AND indisclustered;
+
+-- Verify that clustering all tables does in fact cluster the right ones
+CREATE USER clstr_user;
+CREATE TABLE clstr_1 (a INT PRIMARY KEY);
+CREATE TABLE clstr_2 (a INT PRIMARY KEY);
+CREATE TABLE clstr_3 (a INT PRIMARY KEY);
+ALTER TABLE clstr_1 OWNER TO clstr_user;
+ALTER TABLE clstr_3 OWNER TO clstr_user;
+GRANT SELECT ON clstr_2 TO clstr_user;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+
+-- "CLUSTER <tablename>" on a table that hasn't been clustered
+CLUSTER clstr_2;
+
+CLUSTER clstr_1_pkey ON clstr_1;
+CLUSTER clstr_2_pkey ON clstr_2;
+SELECT * FROM clstr_1 UNION ALL
+  SELECT * FROM clstr_2 UNION ALL
+  SELECT * FROM clstr_3;
+
+-- revert to the original state
+DELETE FROM clstr_1;
+DELETE FROM clstr_2;
+DELETE FROM clstr_3;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+INSERT INTO clstr_2 VALUES (2);
+INSERT INTO clstr_2 VALUES (1);
+INSERT INTO clstr_3 VALUES (2);
+INSERT INTO clstr_3 VALUES (1);
+
+-- this user can only cluster clstr_1 and clstr_3, but the latter
+-- has not been clustered
+SET SESSION AUTHORIZATION clstr_user;
+CLUSTER ALL;
+SELECT * FROM clstr_1 UNION ALL
+  SELECT * FROM clstr_2 UNION ALL
+  SELECT * FROM clstr_3;
+
+-- cluster a single table using the indisclustered bit previously set
+DELETE FROM clstr_1;
+INSERT INTO clstr_1 VALUES (2);
+INSERT INTO clstr_1 VALUES (1);
+CLUSTER clstr_1;
+SELECT * FROM clstr_1;
+
+-- clean up
+\c -
+DROP TABLE clstr_1;
+DROP TABLE clstr_3;
+DROP USER clstr_user;