Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * cluster.c
4 : : * CLUSTER a table on an index. This is now also used for VACUUM FULL.
5 : : *
6 : : * There is hardly anything left of Paul Brown's original implementation...
7 : : *
8 : : *
9 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994-5, Regents of the University of California
11 : : *
12 : : *
13 : : * IDENTIFICATION
14 : : * src/backend/commands/cluster.c
15 : : *
16 : : *-------------------------------------------------------------------------
17 : : */
18 : : #include "postgres.h"
19 : :
20 : : #include "access/amapi.h"
21 : : #include "access/heapam.h"
22 : : #include "access/multixact.h"
23 : : #include "access/relscan.h"
24 : : #include "access/tableam.h"
25 : : #include "access/toast_internals.h"
26 : : #include "access/transam.h"
27 : : #include "access/xact.h"
28 : : #include "catalog/catalog.h"
29 : : #include "catalog/dependency.h"
30 : : #include "catalog/heap.h"
31 : : #include "catalog/index.h"
32 : : #include "catalog/namespace.h"
33 : : #include "catalog/objectaccess.h"
34 : : #include "catalog/pg_am.h"
35 : : #include "catalog/pg_database.h"
36 : : #include "catalog/pg_inherits.h"
37 : : #include "catalog/toasting.h"
38 : : #include "commands/cluster.h"
39 : : #include "commands/defrem.h"
40 : : #include "commands/progress.h"
41 : : #include "commands/tablecmds.h"
42 : : #include "commands/vacuum.h"
43 : : #include "miscadmin.h"
44 : : #include "optimizer/optimizer.h"
45 : : #include "pgstat.h"
46 : : #include "storage/bufmgr.h"
47 : : #include "storage/lmgr.h"
48 : : #include "storage/predicate.h"
49 : : #include "utils/acl.h"
50 : : #include "utils/fmgroids.h"
51 : : #include "utils/guc.h"
52 : : #include "utils/inval.h"
53 : : #include "utils/lsyscache.h"
54 : : #include "utils/memutils.h"
55 : : #include "utils/pg_rusage.h"
56 : : #include "utils/relmapper.h"
57 : : #include "utils/snapmgr.h"
58 : : #include "utils/syscache.h"
59 : :
60 : : /*
61 : : * This struct is used to pass around the information on tables to be
62 : : * clustered. We need this so we can make a list of them when invoked without
63 : : * a specific table/index pair.
64 : : */
65 : : typedef struct
66 : : {
67 : : Oid tableOid;
68 : : Oid indexOid;
69 : : } RelToCluster;
70 : :
71 : :
72 : : static void cluster_multiple_rels(List *rtcs, ClusterParams *params);
73 : : static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
74 : : static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
75 : : bool verbose, bool *pSwapToastByContent,
76 : : TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
77 : : static List *get_tables_to_cluster(MemoryContext cluster_context);
78 : : static List *get_tables_to_cluster_partitioned(MemoryContext cluster_context,
79 : : Oid indexOid);
80 : : static bool cluster_is_permitted_for_relation(Oid relid, Oid userid);
81 : :
82 : :
83 : : /*---------------------------------------------------------------------------
84 : : * This cluster code allows for clustering multiple tables at once. Because
85 : : * of this, we cannot just run everything on a single transaction, or we
86 : : * would be forced to acquire exclusive locks on all the tables being
87 : : * clustered, simultaneously --- very likely leading to deadlock.
88 : : *
89 : : * To solve this we follow a similar strategy to VACUUM code,
90 : : * clustering each relation in a separate transaction. For this to work,
91 : : * we need to:
92 : : * - provide a separate memory context so that we can pass information in
93 : : * a way that survives across transactions
94 : : * - start a new transaction every time a new relation is clustered
95 : : * - check for validity of the information on to-be-clustered relations,
96 : : * as someone might have deleted a relation behind our back, or
97 : : * clustered one on a different index
98 : : * - end the transaction
99 : : *
100 : : * The single-relation case does not have any such overhead.
101 : : *
102 : : * We also allow a relation to be specified without index. In that case,
103 : : * the indisclustered bit will be looked up, and an ERROR will be thrown
104 : : * if there is no index with the bit set.
105 : : *---------------------------------------------------------------------------
106 : : */
107 : : void
1228 michael@paquier.xyz 108 :CBC 114 : cluster(ParseState *pstate, ClusterStmt *stmt, bool isTopLevel)
109 : : {
110 : : ListCell *lc;
1182 111 : 114 : ClusterParams params = {0};
1228 112 : 114 : bool verbose = false;
743 alvherre@alvh.no-ip. 113 : 114 : Relation rel = NULL;
114 : 114 : Oid indexOid = InvalidOid;
115 : : MemoryContext cluster_context;
116 : : List *rtcs;
117 : :
118 : : /* Parse option list */
1228 michael@paquier.xyz 119 [ + + + + : 116 : foreach(lc, stmt->params)
+ + ]
120 : : {
121 : 2 : DefElem *opt = (DefElem *) lfirst(lc);
122 : :
123 [ + - ]: 2 : if (strcmp(opt->defname, "verbose") == 0)
124 : 2 : verbose = defGetBoolean(opt);
125 : : else
1228 michael@paquier.xyz 126 [ # # ]:UBC 0 : ereport(ERROR,
127 : : (errcode(ERRCODE_SYNTAX_ERROR),
128 : : errmsg("unrecognized CLUSTER option \"%s\"",
129 : : opt->defname),
130 : : parser_errposition(pstate, opt->location)));
131 : : }
132 : :
1182 michael@paquier.xyz 133 :CBC 114 : params.options = (verbose ? CLUOPT_VERBOSE : 0);
134 : :
7776 tgl@sss.pgh.pa.us 135 [ + + ]: 114 : if (stmt->relation != NULL)
136 : : {
137 : : /* This is the single-relation case. */
138 : : Oid tableOid;
139 : :
140 : : /*
141 : : * Find, lock, and check permissions on the table. We obtain
142 : : * AccessExclusiveLock right away to avoid lock-upgrade hazard in the
143 : : * single-transaction case.
144 : : */
4498 rhaas@postgresql.org 145 : 104 : tableOid = RangeVarGetRelidExtended(stmt->relation,
146 : : AccessExclusiveLock,
147 : : 0,
148 : : RangeVarCallbackMaintainsTable,
149 : : NULL);
1910 andres@anarazel.de 150 : 98 : rel = table_open(tableOid, NoLock);
151 : :
152 : : /*
153 : : * Reject clustering a remote temp table ... their local buffer
154 : : * manager is not going to cope.
155 : : */
5493 tgl@sss.pgh.pa.us 156 [ + + - + ]: 98 : if (RELATION_IS_OTHER_TEMP(rel))
6061 alvherre@alvh.no-ip. 157 [ # # ]:UBC 0 : ereport(ERROR,
158 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
159 : : errmsg("cannot cluster temporary tables of other sessions")));
160 : :
7776 tgl@sss.pgh.pa.us 161 [ + + ]:CBC 98 : if (stmt->indexname == NULL)
162 : : {
163 : : ListCell *index;
164 : :
165 : : /* We need to find the index that has indisclustered set. */
7559 bruce@momjian.us 166 [ + - + + : 23 : foreach(index, RelationGetIndexList(rel))
+ + ]
167 : : {
7263 neilc@samurai.com 168 : 17 : indexOid = lfirst_oid(index);
1469 michael@paquier.xyz 169 [ + + ]: 17 : if (get_index_isclustered(indexOid))
7776 tgl@sss.pgh.pa.us 170 : 11 : break;
171 : 6 : indexOid = InvalidOid;
172 : : }
173 : :
174 [ + + ]: 17 : if (!OidIsValid(indexOid))
7574 175 [ + - ]: 6 : ereport(ERROR,
176 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
177 : : errmsg("there is no previously clustered index for table \"%s\"",
178 : : stmt->relation->relname)));
179 : : }
180 : : else
181 : : {
182 : : /*
183 : : * The index is expected to be in the same namespace as the
184 : : * relation.
185 : : */
7776 186 : 81 : indexOid = get_relname_relid(stmt->indexname,
187 : 81 : rel->rd_rel->relnamespace);
188 [ - + ]: 81 : if (!OidIsValid(indexOid))
7574 tgl@sss.pgh.pa.us 189 [ # # ]:UBC 0 : ereport(ERROR,
190 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
191 : : errmsg("index \"%s\" for table \"%s\" does not exist",
192 : : stmt->indexname, stmt->relation->relname)));
193 : : }
194 : :
743 alvherre@alvh.no-ip. 195 [ + + ]:CBC 92 : if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
196 : : {
197 : : /* close relation, keep lock till commit */
198 : 79 : table_close(rel, NoLock);
199 : :
200 : : /* Do the job. */
201 : 79 : cluster_rel(tableOid, indexOid, ¶ms);
202 : :
203 : 79 : return;
204 : : }
205 : : }
206 : :
207 : : /*
208 : : * By here, we know we are in a multi-table situation. In order to avoid
209 : : * holding locks for too long, we want to process each table in its own
210 : : * transaction. This forces us to disallow running inside a user
211 : : * transaction block.
212 : : */
213 : 23 : PreventInTransactionBlock(isTopLevel, "CLUSTER");
214 : :
215 : : /* Also, we need a memory context to hold our list of relations */
216 : 23 : cluster_context = AllocSetContextCreate(PortalContext,
217 : : "Cluster",
218 : : ALLOCSET_DEFAULT_SIZES);
219 : :
220 : : /*
221 : : * Either we're processing a partitioned table, or we were not given any
222 : : * table name at all. In either case, obtain a list of relations to
223 : : * process.
224 : : *
225 : : * In the former case, an index name must have been given, so we don't
226 : : * need to recheck its "indisclustered" bit, but we have to check that it
227 : : * is an index that we can cluster on. In the latter case, we set the
228 : : * option bit to have indisclustered verified.
229 : : *
230 : : * Rechecking the relation itself is necessary here in all cases.
231 : : */
232 : 23 : params.options |= CLUOPT_RECHECK;
233 [ + + ]: 23 : if (rel != NULL)
234 : : {
235 [ - + ]: 13 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
732 michael@paquier.xyz 236 : 13 : check_index_is_clusterable(rel, indexOid, AccessShareLock);
743 alvherre@alvh.no-ip. 237 : 10 : rtcs = get_tables_to_cluster_partitioned(cluster_context, indexOid);
238 : :
239 : : /* close relation, releasing lock on parent table */
240 : 10 : table_close(rel, AccessExclusiveLock);
241 : : }
242 : : else
243 : : {
244 : 10 : rtcs = get_tables_to_cluster(cluster_context);
245 : 10 : params.options |= CLUOPT_RECHECK_ISCLUSTERED;
246 : : }
247 : :
248 : : /* Do the job. */
249 : 20 : cluster_multiple_rels(rtcs, ¶ms);
250 : :
251 : : /* Start a new transaction for the cleanup work. */
252 : 20 : StartTransactionCommand();
253 : :
254 : : /* Clean up working storage */
255 : 20 : MemoryContextDelete(cluster_context);
256 : : }
257 : :
258 : : /*
259 : : * Given a list of relations to cluster, process each of them in a separate
260 : : * transaction.
261 : : *
262 : : * We expect to be in a transaction at start, but there isn't one when we
263 : : * return.
264 : : */
265 : : static void
266 : 20 : cluster_multiple_rels(List *rtcs, ClusterParams *params)
267 : : {
268 : : ListCell *lc;
269 : :
270 : : /* Commit to get out of starting transaction */
271 : 20 : PopActiveSnapshot();
272 : 20 : CommitTransactionCommand();
273 : :
274 : : /* Cluster the tables, each in a separate transaction */
275 [ + + + + : 35 : foreach(lc, rtcs)
+ + ]
276 : : {
277 : 15 : RelToCluster *rtc = (RelToCluster *) lfirst(lc);
278 : :
279 : : /* Start a new transaction for each relation. */
7641 tgl@sss.pgh.pa.us 280 : 15 : StartTransactionCommand();
281 : :
282 : : /* functions in indexes may want a snapshot set */
743 alvherre@alvh.no-ip. 283 : 15 : PushActiveSnapshot(GetTransactionSnapshot());
284 : :
285 : : /* Do the job. */
286 : 15 : cluster_rel(rtc->tableOid, rtc->indexOid, params);
287 : :
288 : 15 : PopActiveSnapshot();
289 : 15 : CommitTransactionCommand();
290 : : }
7776 tgl@sss.pgh.pa.us 291 : 20 : }
292 : :
293 : : /*
294 : : * cluster_rel
295 : : *
296 : : * This clusters the table by creating a new, clustered table and
297 : : * swapping the relfilenumbers of the new table and the old table, so
298 : : * the OID of the original table is preserved. Thus we do not lose
299 : : * GRANT, inheritance nor references to this table (this was a bug
300 : : * in releases through 7.3).
301 : : *
302 : : * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
303 : : * the new table, it's better to create the indexes afterwards than to fill
304 : : * them incrementally while we load the table.
305 : : *
306 : : * If indexOid is InvalidOid, the table will be rewritten in physical order
307 : : * instead of index order. This is the new implementation of VACUUM FULL,
308 : : * and error messages should refer to the operation as VACUUM not CLUSTER.
309 : : */
310 : : void
1182 michael@paquier.xyz 311 : 263 : cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
312 : : {
313 : : Relation OldHeap;
314 : : Oid save_userid;
315 : : int save_sec_context;
316 : : int save_nestlevel;
317 : 263 : bool verbose = ((params->options & CLUOPT_VERBOSE) != 0);
318 : 263 : bool recheck = ((params->options & CLUOPT_RECHECK) != 0);
319 : :
320 : : /* Check for user-requested abort. */
7821 bruce@momjian.us 321 [ - + ]: 263 : CHECK_FOR_INTERRUPTS();
322 : :
1847 rhaas@postgresql.org 323 : 263 : pgstat_progress_start_command(PROGRESS_COMMAND_CLUSTER, tableOid);
324 [ + + ]: 263 : if (OidIsValid(indexOid))
325 : 94 : pgstat_progress_update_param(PROGRESS_CLUSTER_COMMAND,
326 : : PROGRESS_CLUSTER_COMMAND_CLUSTER);
327 : : else
328 : 169 : pgstat_progress_update_param(PROGRESS_CLUSTER_COMMAND,
329 : : PROGRESS_CLUSTER_COMMAND_VACUUM_FULL);
330 : :
331 : : /*
332 : : * We grab exclusive access to the target rel and index for the duration
333 : : * of the transaction. (This is redundant for the single-transaction
334 : : * case, since cluster() already did it.) The index lock is taken inside
335 : : * check_index_is_clusterable.
336 : : */
5212 itagaki.takahiro@gma 337 : 263 : OldHeap = try_relation_open(tableOid, AccessExclusiveLock);
338 : :
339 : : /* If the table has gone away, we can skip processing it */
6449 tgl@sss.pgh.pa.us 340 [ - + ]: 263 : if (!OldHeap)
341 : : {
1847 rhaas@postgresql.org 342 :UBC 0 : pgstat_progress_end_command();
6449 tgl@sss.pgh.pa.us 343 : 0 : return;
344 : : }
345 : :
346 : : /*
347 : : * Switch to the table owner's userid, so that any index functions are run
348 : : * as that user. Also lock down security-restricted operations and
349 : : * arrange to make GUC variable changes local to this command.
350 : : */
706 noah@leadboat.com 351 :CBC 263 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
352 : 263 : SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
353 : : save_sec_context | SECURITY_RESTRICTED_OPERATION);
354 : 263 : save_nestlevel = NewGUCNestLevel();
41 jdavis@postgresql.or 355 :GNC 263 : RestrictSearchPath();
356 : :
357 : : /*
358 : : * Since we may open a new transaction for each relation, we have to check
359 : : * that the relation still is what we think it is.
360 : : *
361 : : * If this is a single-transaction CLUSTER, we can skip these tests. We
362 : : * *must* skip the one on indisclustered since it would reject an attempt
363 : : * to cluster a not-previously-clustered index.
364 : : */
7776 tgl@sss.pgh.pa.us 365 [ + + ]:CBC 263 : if (recheck)
366 : : {
367 : : /* Check that the user still has privileges for the relation */
32 nathan@postgresql.or 368 [ - + ]:GNC 15 : if (!cluster_is_permitted_for_relation(tableOid, save_userid))
369 : : {
6449 tgl@sss.pgh.pa.us 370 :UBC 0 : relation_close(OldHeap, AccessExclusiveLock);
706 noah@leadboat.com 371 : 0 : goto out;
372 : : }
373 : :
374 : : /*
375 : : * Silently skip a temp table for a remote session. Only doing this
376 : : * check in the "recheck" case is appropriate (which currently means
377 : : * somebody is executing a database-wide CLUSTER or on a partitioned
378 : : * table), because there is another check in cluster() which will stop
379 : : * any attempt to cluster remote temp tables by name. There is
380 : : * another check in cluster_rel which is redundant, but we leave it
381 : : * for extra safety.
382 : : */
5493 tgl@sss.pgh.pa.us 383 [ - + - - ]:CBC 15 : if (RELATION_IS_OTHER_TEMP(OldHeap))
384 : : {
6061 alvherre@alvh.no-ip. 385 :UBC 0 : relation_close(OldHeap, AccessExclusiveLock);
706 noah@leadboat.com 386 : 0 : goto out;
387 : : }
388 : :
5212 itagaki.takahiro@gma 389 [ + - ]:CBC 15 : if (OidIsValid(indexOid))
390 : : {
391 : : /*
392 : : * Check that the index still exists
393 : : */
5173 rhaas@postgresql.org 394 [ - + ]: 15 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
395 : : {
5212 itagaki.takahiro@gma 396 :UBC 0 : relation_close(OldHeap, AccessExclusiveLock);
706 noah@leadboat.com 397 : 0 : goto out;
398 : : }
399 : :
400 : : /*
401 : : * Check that the index is still the one with indisclustered set,
402 : : * if needed.
403 : : */
743 alvherre@alvh.no-ip. 404 [ + + ]:CBC 15 : if ((params->options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
405 [ - + ]: 3 : !get_index_isclustered(indexOid))
406 : : {
5212 itagaki.takahiro@gma 407 :UBC 0 : relation_close(OldHeap, AccessExclusiveLock);
706 noah@leadboat.com 408 : 0 : goto out;
409 : : }
410 : : }
411 : : }
412 : :
413 : : /*
414 : : * We allow VACUUM FULL, but not CLUSTER, on shared catalogs. CLUSTER
415 : : * would work in most respects, but the index would only get marked as
416 : : * indisclustered in the current database, leading to unexpected behavior
417 : : * if CLUSTER were later invoked in another database.
418 : : */
5180 tgl@sss.pgh.pa.us 419 [ + + - + ]:CBC 263 : if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared)
5180 tgl@sss.pgh.pa.us 420 [ # # ]:UBC 0 : ereport(ERROR,
421 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
422 : : errmsg("cannot cluster a shared catalog")));
423 : :
424 : : /*
425 : : * Don't process temp tables of other backends ... their local buffer
426 : : * manager is not going to cope.
427 : : */
5180 tgl@sss.pgh.pa.us 428 [ + + - + ]:CBC 263 : if (RELATION_IS_OTHER_TEMP(OldHeap))
429 : : {
5180 tgl@sss.pgh.pa.us 430 [ # # ]:UBC 0 : if (OidIsValid(indexOid))
431 [ # # ]: 0 : ereport(ERROR,
432 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
433 : : errmsg("cannot cluster temporary tables of other sessions")));
434 : : else
435 [ # # ]: 0 : ereport(ERROR,
436 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
437 : : errmsg("cannot vacuum temporary tables of other sessions")));
438 : : }
439 : :
440 : : /*
441 : : * Also check for active uses of the relation in the current transaction,
442 : : * including open scans and pending AFTER trigger events.
443 : : */
5180 tgl@sss.pgh.pa.us 444 [ + + ]:CBC 263 : CheckTableNotInUse(OldHeap, OidIsValid(indexOid) ? "CLUSTER" : "VACUUM");
445 : :
446 : : /* Check heap and index are valid to cluster on */
447 [ + + ]: 263 : if (OidIsValid(indexOid))
732 michael@paquier.xyz 448 : 94 : check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock);
449 : :
450 : : /*
451 : : * Quietly ignore the request if this is a materialized view which has not
452 : : * been populated from its query. No harm is done because there is no data
453 : : * to deal with, and we don't want to throw an error if this is part of a
454 : : * multi-relation request -- for example, CLUSTER was run on the entire
455 : : * database.
456 : : */
4060 kgrittn@postgresql.o 457 [ - + ]: 263 : if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
3996 tgl@sss.pgh.pa.us 458 [ # # ]:UBC 0 : !RelationIsPopulated(OldHeap))
459 : : {
4060 kgrittn@postgresql.o 460 : 0 : relation_close(OldHeap, AccessExclusiveLock);
706 noah@leadboat.com 461 : 0 : goto out;
462 : : }
463 : :
743 alvherre@alvh.no-ip. 464 [ + + + - :CBC 263 : Assert(OldHeap->rd_rel->relkind == RELKIND_RELATION ||
- + ]
465 : : OldHeap->rd_rel->relkind == RELKIND_MATVIEW ||
466 : : OldHeap->rd_rel->relkind == RELKIND_TOASTVALUE);
467 : :
468 : : /*
469 : : * All predicate locks on the tuples or pages are about to be made
470 : : * invalid, because we move tuples around. Promote them to relation
471 : : * locks. Predicate locks on indexes will be promoted when they are
472 : : * reindexed.
473 : : */
4694 heikki.linnakangas@i 474 : 263 : TransferPredicateLocksToHeapRelation(OldHeap);
475 : :
476 : : /* rebuild_relation does all the dirty work */
1264 andres@anarazel.de 477 : 263 : rebuild_relation(OldHeap, indexOid, verbose);
478 : :
479 : : /* NB: rebuild_relation does table_close() on OldHeap */
480 : :
706 noah@leadboat.com 481 : 260 : out:
482 : : /* Roll back any GUC changes executed by index functions */
483 : 260 : AtEOXact_GUC(false, save_nestlevel);
484 : :
485 : : /* Restore userid and security context */
486 : 260 : SetUserIdAndSecContext(save_userid, save_sec_context);
487 : :
1847 rhaas@postgresql.org 488 : 260 : pgstat_progress_end_command();
489 : : }
490 : :
491 : : /*
492 : : * Verify that the specified heap and index are valid to cluster on
493 : : *
494 : : * Side effect: obtains lock on the index. The caller may
495 : : * in some cases already have AccessExclusiveLock on the table, but
496 : : * not in all cases so we can't rely on the table-level lock for
497 : : * protection here.
498 : : */
499 : : void
732 michael@paquier.xyz 500 : 139 : check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode)
501 : : {
502 : : Relation OldIndex;
503 : :
5008 simon@2ndQuadrant.co 504 : 139 : OldIndex = index_open(indexOid, lockmode);
505 : :
506 : : /*
507 : : * Check that index is in fact an index on the given relation
508 : : */
7917 tgl@sss.pgh.pa.us 509 [ + - ]: 139 : if (OldIndex->rd_index == NULL ||
7283 510 [ - + ]: 139 : OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap))
7574 tgl@sss.pgh.pa.us 511 [ # # ]:UBC 0 : ereport(ERROR,
512 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
513 : : errmsg("\"%s\" is not an index for table \"%s\"",
514 : : RelationGetRelationName(OldIndex),
515 : : RelationGetRelationName(OldHeap))));
516 : :
517 : : /* Index AM must allow clustering */
1910 andres@anarazel.de 518 [ - + ]:CBC 139 : if (!OldIndex->rd_indam->amclusterable)
5180 tgl@sss.pgh.pa.us 519 [ # # ]:UBC 0 : ereport(ERROR,
520 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
521 : : errmsg("cannot cluster on index \"%s\" because access method does not support clustering",
522 : : RelationGetRelationName(OldIndex))));
523 : :
524 : : /*
525 : : * Disallow clustering on incomplete indexes (those that might not index
526 : : * every row of the relation). We could relax this by making a separate
527 : : * seqscan pass over the table to copy the missing rows, but that seems
528 : : * expensive and tedious.
529 : : */
2209 andrew@dunslane.net 530 [ - + ]:CBC 139 : if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred, NULL))
7574 tgl@sss.pgh.pa.us 531 [ # # ]:UBC 0 : ereport(ERROR,
532 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
533 : : errmsg("cannot cluster on partial index \"%s\"",
534 : : RelationGetRelationName(OldIndex))));
535 : :
536 : : /*
537 : : * Disallow if index is left over from a failed CREATE INDEX CONCURRENTLY;
538 : : * it might well not contain entries for every heap row, or might not even
539 : : * be internally consistent. (But note that we don't check indcheckxmin;
540 : : * the worst consequence of following broken HOT chains would be that we
541 : : * might put recently-dead tuples out-of-order in the new table, and there
542 : : * is little harm in that.)
543 : : */
1935 peter_e@gmx.net 544 [ + + ]:CBC 139 : if (!OldIndex->rd_index->indisvalid)
6042 tgl@sss.pgh.pa.us 545 [ + - ]: 3 : ereport(ERROR,
546 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
547 : : errmsg("cannot cluster on invalid index \"%s\"",
548 : : RelationGetRelationName(OldIndex))));
549 : :
550 : : /* Drop relcache refcnt on OldIndex, but keep lock */
6467 551 : 136 : index_close(OldIndex, NoLock);
7813 bruce@momjian.us 552 : 136 : }
553 : :
554 : : /*
555 : : * mark_index_clustered: mark the specified index as the one clustered on
556 : : *
557 : : * With indexOid == InvalidOid, will mark all indexes of rel not-clustered.
558 : : */
559 : : void
4046 rhaas@postgresql.org 560 : 135 : mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
561 : : {
562 : : HeapTuple indexTuple;
563 : : Form_pg_index indexForm;
564 : : Relation pg_index;
565 : : ListCell *index;
566 : :
567 : : /* Disallow applying to a partitioned table */
2271 alvherre@alvh.no-ip. 568 [ + + ]: 135 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
569 [ + - ]: 6 : ereport(ERROR,
570 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
571 : : errmsg("cannot mark index clustered in partitioned table")));
572 : :
573 : : /*
574 : : * If the index is already marked clustered, no need to do anything.
575 : : */
7281 tgl@sss.pgh.pa.us 576 [ + + ]: 129 : if (OidIsValid(indexOid))
577 : : {
1469 michael@paquier.xyz 578 [ + + ]: 123 : if (get_index_isclustered(indexOid))
7281 tgl@sss.pgh.pa.us 579 : 20 : return;
580 : : }
581 : :
582 : : /*
583 : : * Check each index of the relation and set/clear the bit as needed.
584 : : */
1910 andres@anarazel.de 585 : 109 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
586 : :
7281 tgl@sss.pgh.pa.us 587 [ + - + + : 321 : foreach(index, RelationGetIndexList(rel))
+ + ]
588 : : {
7168 bruce@momjian.us 589 : 212 : Oid thisIndexOid = lfirst_oid(index);
590 : :
5173 rhaas@postgresql.org 591 : 212 : indexTuple = SearchSysCacheCopy1(INDEXRELID,
592 : : ObjectIdGetDatum(thisIndexOid));
7281 tgl@sss.pgh.pa.us 593 [ - + ]: 212 : if (!HeapTupleIsValid(indexTuple))
7281 tgl@sss.pgh.pa.us 594 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
7281 tgl@sss.pgh.pa.us 595 :CBC 212 : indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
596 : :
597 : : /*
598 : : * Unset the bit if set. We know it's wrong because we checked this
599 : : * earlier.
600 : : */
601 [ + + ]: 212 : if (indexForm->indisclustered)
602 : : {
603 : 15 : indexForm->indisclustered = false;
2630 alvherre@alvh.no-ip. 604 : 15 : CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
605 : : }
7281 tgl@sss.pgh.pa.us 606 [ + + ]: 197 : else if (thisIndexOid == indexOid)
607 : : {
608 : : /* this was checked earlier, but let's be real sure */
1935 peter_e@gmx.net 609 [ - + ]: 103 : if (!indexForm->indisvalid)
4155 tgl@sss.pgh.pa.us 610 [ # # ]:UBC 0 : elog(ERROR, "cannot cluster on invalid index %u", indexOid);
7281 tgl@sss.pgh.pa.us 611 :CBC 103 : indexForm->indisclustered = true;
2630 alvherre@alvh.no-ip. 612 : 103 : CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
613 : : }
614 : :
4046 rhaas@postgresql.org 615 [ - + ]: 212 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
616 : : InvalidOid, is_internal);
617 : :
7281 tgl@sss.pgh.pa.us 618 : 212 : heap_freetuple(indexTuple);
619 : : }
620 : :
1910 andres@anarazel.de 621 : 109 : table_close(pg_index, RowExclusiveLock);
622 : : }
623 : :
624 : : /*
625 : : * rebuild_relation: rebuild an existing relation in index or physical order
626 : : *
627 : : * OldHeap: table to rebuild --- must be opened and exclusive-locked!
628 : : * indexOid: index to cluster by, or InvalidOid to rewrite in physical order.
629 : : *
630 : : * NB: this routine closes OldHeap at the right time; caller should not.
631 : : */
632 : : static void
1264 633 : 263 : rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
634 : : {
7776 tgl@sss.pgh.pa.us 635 : 263 : Oid tableOid = RelationGetRelid(OldHeap);
991 michael@paquier.xyz 636 : 263 : Oid accessMethod = OldHeap->rd_rel->relam;
7217 tgl@sss.pgh.pa.us 637 : 263 : Oid tableSpace = OldHeap->rd_rel->reltablespace;
638 : : Oid OIDNewHeap;
639 : : char relpersistence;
640 : : bool is_system_catalog;
641 : : bool swap_toast_by_content;
642 : : TransactionId frozenXid;
643 : : MultiXactId cutoffMulti;
644 : :
5212 itagaki.takahiro@gma 645 [ + + ]: 263 : if (OidIsValid(indexOid))
646 : : /* Mark the correct index as clustered */
4046 rhaas@postgresql.org 647 : 94 : mark_index_clustered(OldHeap, indexOid, true);
648 : :
649 : : /* Remember info about rel before closing OldHeap */
2598 tgl@sss.pgh.pa.us 650 : 263 : relpersistence = OldHeap->rd_rel->relpersistence;
5180 651 : 263 : is_system_catalog = IsSystemRelation(OldHeap);
652 : :
653 : : /* Close relcache entry, but keep lock until transaction commit */
1910 andres@anarazel.de 654 : 263 : table_close(OldHeap, NoLock);
655 : :
656 : : /* Create the transient table that will receive the re-ordered data */
3523 alvherre@alvh.no-ip. 657 : 263 : OIDNewHeap = make_new_heap(tableOid, tableSpace,
658 : : accessMethod,
659 : : relpersistence,
660 : : AccessExclusiveLock);
661 : :
662 : : /* Copy the heap data into the new table in the desired order */
1264 andres@anarazel.de 663 : 263 : copy_table_data(OIDNewHeap, tableOid, indexOid, verbose,
664 : : &swap_toast_by_content, &frozenXid, &cutoffMulti);
665 : :
666 : : /*
667 : : * Swap the physical files of the target and transient tables, then
668 : : * rebuild the target's indexes and throw away the transient table.
669 : : */
5180 tgl@sss.pgh.pa.us 670 : 263 : finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
671 : : swap_toast_by_content, false, true,
672 : : frozenXid, cutoffMulti,
673 : : relpersistence);
10141 scrappy@hub.org 674 : 260 : }
675 : :
676 : :
677 : : /*
678 : : * Create the transient table that will be filled with new data during
679 : : * CLUSTER, ALTER TABLE, and similar operations. The transient table
680 : : * duplicates the logical structure of the OldHeap; but will have the
681 : : * specified physical storage properties NewTableSpace, NewAccessMethod, and
682 : : * relpersistence.
683 : : *
684 : : * After this, the caller should load the new heap with transferred/modified
685 : : * data, then call finish_heap_swap to complete the operation.
686 : : */
687 : : Oid
991 michael@paquier.xyz 688 : 820 : make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
689 : : char relpersistence, LOCKMODE lockmode)
690 : : {
691 : : TupleDesc OldHeapDesc;
692 : : char NewHeapName[NAMEDATALEN];
693 : : Oid OIDNewHeap;
694 : : Oid toastid;
695 : : Relation OldHeap;
696 : : HeapTuple tuple;
697 : : Datum reloptions;
698 : : bool isNull;
699 : : Oid namespaceid;
700 : :
1910 andres@anarazel.de 701 : 820 : OldHeap = table_open(OIDOldHeap, lockmode);
9357 bruce@momjian.us 702 : 820 : OldHeapDesc = RelationGetDescr(OldHeap);
703 : :
704 : : /*
705 : : * Note that the NewHeap will not receive any of the defaults or
706 : : * constraints associated with the OldHeap; we don't need 'em, and there's
707 : : * no reason to spend cycles inserting them into the catalogs only to
708 : : * delete them.
709 : : */
710 : :
711 : : /*
712 : : * But we do want to use reloptions of the old heap for new heap.
713 : : */
5173 rhaas@postgresql.org 714 : 820 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(OIDOldHeap));
6495 tgl@sss.pgh.pa.us 715 [ - + ]: 820 : if (!HeapTupleIsValid(tuple))
6495 tgl@sss.pgh.pa.us 716 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", OIDOldHeap);
6495 tgl@sss.pgh.pa.us 717 :CBC 820 : reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
718 : : &isNull);
719 [ + + ]: 820 : if (isNull)
720 : 801 : reloptions = (Datum) 0;
721 : :
3523 alvherre@alvh.no-ip. 722 [ + + ]: 820 : if (relpersistence == RELPERSISTENCE_TEMP)
3925 kgrittn@postgresql.o 723 : 73 : namespaceid = LookupCreationNamespace("pg_temp");
724 : : else
725 : 747 : namespaceid = RelationGetNamespace(OldHeap);
726 : :
727 : : /*
728 : : * Create the new heap, using a temporary name in the same namespace as
729 : : * the existing table. NOTE: there is some risk of collision with user
730 : : * relnames. Working around this seems more trouble than it's worth; in
731 : : * particular, we can't create the new heap in a different namespace from
732 : : * the old, or we will have problems with the TEMP status of temp tables.
733 : : *
734 : : * Note: the new heap is not a shared relation, even if we are rebuilding
735 : : * a shared rel. However, we do make the new heap mapped if the source is
736 : : * mapped. This simplifies swap_relation_files, and is absolutely
737 : : * necessary for rebuilding pg_class, for reasons explained there.
738 : : */
5183 tgl@sss.pgh.pa.us 739 : 820 : snprintf(NewHeapName, sizeof(NewHeapName), "pg_temp_%u", OIDOldHeap);
740 : :
741 : 820 : OIDNewHeap = heap_create_with_catalog(NewHeapName,
742 : : namespaceid,
743 : : NewTableSpace,
744 : : InvalidOid,
745 : : InvalidOid,
746 : : InvalidOid,
6806 747 : 820 : OldHeap->rd_rel->relowner,
748 : : NewAccessMethod,
749 : : OldHeapDesc,
750 : : NIL,
751 : : RELKIND_RELATION,
752 : : relpersistence,
753 : : false,
5180 754 [ + + + - : 820 : RelationIsMapped(OldHeap),
+ - + + +
- + + ]
755 : : ONCOMMIT_NOOP,
756 : : reloptions,
757 : : false,
758 : : true,
759 : : true,
760 : : OIDOldHeap,
3330 alvherre@alvh.no-ip. 761 :ECB (746) : NULL);
5012 rhaas@postgresql.org 762 [ - + ]:CBC 820 : Assert(OIDNewHeap != InvalidOid);
763 : :
6496 bruce@momjian.us 764 : 820 : ReleaseSysCache(tuple);
765 : :
766 : : /*
767 : : * Advance command counter so that the newly-created relation's catalog
768 : : * tuples will be visible to table_open.
769 : : */
8504 tgl@sss.pgh.pa.us 770 : 820 : CommandCounterIncrement();
771 : :
772 : : /*
773 : : * If necessary, create a TOAST table for the new relation.
774 : : *
775 : : * If the relation doesn't have a TOAST table already, we can't need one
776 : : * for the new relation. The other way around is possible though: if some
777 : : * wide columns have been dropped, NewHeapCreateToastTable can decide that
778 : : * no TOAST table is needed for the new table.
779 : : *
780 : : * Note that NewHeapCreateToastTable ends with CommandCounterIncrement, so
781 : : * that the TOAST table will be visible for insertion.
782 : : */
5550 alvherre@alvh.no-ip. 783 : 820 : toastid = OldHeap->rd_rel->reltoastrelid;
784 [ + + ]: 820 : if (OidIsValid(toastid))
785 : : {
786 : : /* keep the existing toast table's reloptions, if any */
5173 rhaas@postgresql.org 787 : 332 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
5550 alvherre@alvh.no-ip. 788 [ - + ]: 332 : if (!HeapTupleIsValid(tuple))
5550 alvherre@alvh.no-ip. 789 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
5550 alvherre@alvh.no-ip. 790 :CBC 332 : reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
791 : : &isNull);
792 [ + - ]: 332 : if (isNull)
793 : 332 : reloptions = (Datum) 0;
794 : :
963 akapila@postgresql.o 795 : 332 : NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
796 : :
5550 alvherre@alvh.no-ip. 797 : 332 : ReleaseSysCache(tuple);
798 : : }
799 : :
1910 andres@anarazel.de 800 : 820 : table_close(OldHeap, NoLock);
801 : :
8558 tgl@sss.pgh.pa.us 802 : 820 : return OIDNewHeap;
803 : : }
804 : :
805 : : /*
806 : : * Do the physical copying of table data.
807 : : *
808 : : * There are three output parameters:
809 : : * *pSwapToastByContent is set true if toast tables must be swapped by content.
810 : : * *pFreezeXid receives the TransactionId used as freeze cutoff point.
811 : : * *pCutoffMulti receives the MultiXactId used as a cutoff point.
812 : : */
813 : : static void
1264 andres@anarazel.de 814 : 263 : copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
815 : : bool *pSwapToastByContent, TransactionId *pFreezeXid,
816 : : MultiXactId *pCutoffMulti)
817 : : {
818 : : Relation NewHeap,
819 : : OldHeap,
820 : : OldIndex;
821 : : Relation relRelation;
822 : : HeapTuple reltup;
823 : : Form_pg_class relform;
824 : : TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
825 : : TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY;
826 : : VacuumParams params;
827 : : struct VacuumCutoffs cutoffs;
828 : : bool use_sort;
4938 tgl@sss.pgh.pa.us 829 : 263 : double num_tuples = 0,
830 : 263 : tups_vacuumed = 0,
831 : 263 : tups_recently_dead = 0;
832 : : BlockNumber num_pages;
833 [ + + ]: 263 : int elevel = verbose ? INFO : DEBUG2;
834 : : PGRUsage ru0;
835 : : char *nspname;
836 : :
837 : 263 : pg_rusage_init(&ru0);
838 : :
839 : : /*
840 : : * Open the relations we need.
841 : : */
1910 andres@anarazel.de 842 : 263 : NewHeap = table_open(OIDNewHeap, AccessExclusiveLock);
843 : 263 : OldHeap = table_open(OIDOldHeap, AccessExclusiveLock);
5212 itagaki.takahiro@gma 844 [ + + ]: 263 : if (OidIsValid(OIDOldIndex))
845 : 94 : OldIndex = index_open(OIDOldIndex, AccessExclusiveLock);
846 : : else
847 : 169 : OldIndex = NULL;
848 : :
849 : : /* Store a copy of the namespace name for logging purposes */
972 dgustafsson@postgres 850 : 263 : nspname = get_namespace_name(RelationGetNamespace(OldHeap));
851 : :
852 : : /*
853 : : * Their tuple descriptors should be exactly alike, but here we only need
854 : : * assume that they have the same number of columns.
855 : : */
7007 tgl@sss.pgh.pa.us 856 : 263 : oldTupDesc = RelationGetDescr(OldHeap);
857 : 263 : newTupDesc = RelationGetDescr(NewHeap);
858 [ - + ]: 263 : Assert(newTupDesc->natts == oldTupDesc->natts);
859 : :
860 : : /*
861 : : * If the OldHeap has a toast table, get lock on the toast table to keep
862 : : * it from being vacuumed. This is needed because autovacuum processes
863 : : * toast tables independently of their main tables, with no lock on the
864 : : * latter. If an autovacuum were to start on the toast table after we
865 : : * compute our OldestXmin below, it would use a later OldestXmin, and then
866 : : * possibly remove as DEAD toast tuples belonging to main tuples we think
867 : : * are only RECENTLY_DEAD. Then we'd fail while trying to copy those
868 : : * tuples.
869 : : *
870 : : * We don't need to open the toast relation here, just lock it. The lock
871 : : * will be held till end of transaction.
872 : : */
4732 873 [ + + ]: 263 : if (OldHeap->rd_rel->reltoastrelid)
874 : 89 : LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
875 : :
876 : : /*
877 : : * If both tables have TOAST tables, perform toast swap by content. It is
878 : : * possible that the old table has a toast table but the new one doesn't,
879 : : * if toastable columns have been dropped. In that case we have to do
880 : : * swap by links. This is okay because swap by content is only essential
881 : : * for system catalogs, and we don't support schema changes for them.
882 : : */
5183 883 [ + + + - ]: 263 : if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid)
884 : : {
885 : 89 : *pSwapToastByContent = true;
886 : :
887 : : /*
888 : : * When doing swap by content, any toast pointers written into NewHeap
889 : : * must use the old toast table's OID, because that's where the toast
890 : : * data will eventually be found. Set this up by setting rd_toastoid.
891 : : * This also tells toast_save_datum() to preserve the toast value
892 : : * OIDs, which we want so as not to invalidate toast pointers in
893 : : * system catalog caches, and to avoid making multiple copies of a
894 : : * single toast value.
895 : : *
896 : : * Note that we must hold NewHeap open until we are done writing data,
897 : : * since the relcache will not guarantee to remember this setting once
898 : : * the relation is closed. Also, this technique depends on the fact
899 : : * that no one will try to read from the NewHeap until after we've
900 : : * finished writing it and swapping the rels --- otherwise they could
901 : : * follow the toast pointers to the wrong place. (It would actually
902 : : * work for values copied over from the old toast table, but not for
903 : : * any values that we toast which were previously not toasted.)
904 : : */
905 : 89 : NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;
906 : : }
907 : : else
908 : 174 : *pSwapToastByContent = false;
909 : :
910 : : /*
911 : : * Compute xids used to freeze and weed out dead tuples and multixacts.
912 : : * Since we're going to rewrite the whole table anyway, there's no reason
913 : : * not to be aggressive about this.
914 : : */
508 pg@bowt.ie 915 : 263 : memset(¶ms, 0, sizeof(VacuumParams));
479 916 : 263 : vacuum_get_cutoffs(OldHeap, ¶ms, &cutoffs);
917 : :
918 : : /*
919 : : * FreezeXid will become the table's new relfrozenxid, and that mustn't go
920 : : * backwards, so take the max.
921 : : */
1818 andres@anarazel.de 922 [ + - + + ]: 526 : if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
479 pg@bowt.ie 923 : 263 : TransactionIdPrecedes(cutoffs.FreezeLimit,
924 : 263 : OldHeap->rd_rel->relfrozenxid))
925 : 43 : cutoffs.FreezeLimit = OldHeap->rd_rel->relfrozenxid;
926 : :
927 : : /*
928 : : * MultiXactCutoff, similarly, shouldn't go backwards either.
929 : : */
1818 andres@anarazel.de 930 [ + - - + ]: 526 : if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
479 pg@bowt.ie 931 : 263 : MultiXactIdPrecedes(cutoffs.MultiXactCutoff,
932 : 263 : OldHeap->rd_rel->relminmxid))
479 pg@bowt.ie 933 :UBC 0 : cutoffs.MultiXactCutoff = OldHeap->rd_rel->relminmxid;
934 : :
935 : : /*
936 : : * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
937 : : * the OldHeap. We know how to use a sort to duplicate the ordering of a
938 : : * btree index, and will use seqscan-and-sort for that case if the planner
939 : : * tells us it's cheaper. Otherwise, always indexscan if an index is
940 : : * provided, else plain seqscan.
941 : : */
4938 tgl@sss.pgh.pa.us 942 [ + + + - ]:CBC 263 : if (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)
943 : 94 : use_sort = plan_cluster_use_sort(OIDOldHeap, OIDOldIndex);
944 : : else
945 : 169 : use_sort = false;
946 : :
947 : : /* Log what we're doing */
1844 andres@anarazel.de 948 [ + + + + ]: 263 : if (OldIndex != NULL && !use_sort)
4938 tgl@sss.pgh.pa.us 949 [ - + ]: 39 : ereport(elevel,
950 : : (errmsg("clustering \"%s.%s\" using index scan on \"%s\"",
951 : : nspname,
952 : : RelationGetRelationName(OldHeap),
953 : : RelationGetRelationName(OldIndex))));
1844 andres@anarazel.de 954 [ + + ]: 224 : else if (use_sort)
4938 tgl@sss.pgh.pa.us 955 [ - + ]: 55 : ereport(elevel,
956 : : (errmsg("clustering \"%s.%s\" using sequential scan and sort",
957 : : nspname,
958 : : RelationGetRelationName(OldHeap))));
959 : : else
960 [ + + ]: 169 : ereport(elevel,
961 : : (errmsg("vacuuming \"%s.%s\"",
962 : : nspname,
963 : : RelationGetRelationName(OldHeap))));
964 : :
965 : : /*
966 : : * Hand off the actual copying to AM specific function, the generic code
967 : : * cannot know how to deal with visibility across AMs. Note that this
968 : : * routine is allowed to set FreezeXid / MultiXactCutoff to different
969 : : * values (e.g. because the AM doesn't use freezing).
970 : : */
1844 andres@anarazel.de 971 : 263 : table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
972 : : cutoffs.OldestXmin, &cutoffs.FreezeLimit,
973 : : &cutoffs.MultiXactCutoff,
974 : : &num_tuples, &tups_vacuumed,
975 : : &tups_recently_dead);
976 : :
977 : : /* return selected values to caller, get set as relfrozenxid/minmxid */
479 pg@bowt.ie 978 : 263 : *pFreezeXid = cutoffs.FreezeLimit;
979 : 263 : *pCutoffMulti = cutoffs.MultiXactCutoff;
980 : :
981 : : /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */
5183 tgl@sss.pgh.pa.us 982 : 263 : NewHeap->rd_toastoid = InvalidOid;
983 : :
2300 teodor@sigaev.ru 984 : 263 : num_pages = RelationGetNumberOfBlocks(NewHeap);
985 : :
986 : : /* Log what we did */
4938 tgl@sss.pgh.pa.us 987 [ + + ]: 263 : ereport(elevel,
988 : : (errmsg("\"%s.%s\": found %.0f removable, %.0f nonremovable row versions in %u pages",
989 : : nspname,
990 : : RelationGetRelationName(OldHeap),
991 : : tups_vacuumed, num_tuples,
992 : : RelationGetNumberOfBlocks(OldHeap)),
993 : : errdetail("%.0f dead row versions cannot be removed yet.\n"
994 : : "%s.",
995 : : tups_recently_dead,
996 : : pg_rusage_show(&ru0))));
997 : :
5212 itagaki.takahiro@gma 998 [ + + ]: 263 : if (OldIndex != NULL)
999 : 94 : index_close(OldIndex, NoLock);
1910 andres@anarazel.de 1000 : 263 : table_close(OldHeap, NoLock);
1001 : 263 : table_close(NewHeap, NoLock);
1002 : :
1003 : : /* Update pg_class to reflect the correct values of pages and tuples. */
1004 : 263 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1005 : :
2300 teodor@sigaev.ru 1006 : 263 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(OIDNewHeap));
1007 [ - + ]: 263 : if (!HeapTupleIsValid(reltup))
2300 teodor@sigaev.ru 1008 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", OIDNewHeap);
2300 teodor@sigaev.ru 1009 :CBC 263 : relform = (Form_pg_class) GETSTRUCT(reltup);
1010 : :
1011 : 263 : relform->relpages = num_pages;
1012 : 263 : relform->reltuples = num_tuples;
1013 : :
1014 : : /* Don't update the stats for pg_class. See swap_relation_files. */
1015 [ + + ]: 263 : if (OIDOldHeap != RelationRelationId)
1016 : 251 : CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
1017 : : else
1018 : 12 : CacheInvalidateRelcacheByTuple(reltup);
1019 : :
1020 : : /* Clean up. */
1021 : 263 : heap_freetuple(reltup);
1910 andres@anarazel.de 1022 : 263 : table_close(relRelation, RowExclusiveLock);
1023 : :
1024 : : /* Make the update visible */
2300 teodor@sigaev.ru 1025 : 263 : CommandCounterIncrement();
10141 scrappy@hub.org 1026 : 263 : }
1027 : :
1028 : : /*
1029 : : * Swap the physical files of two given relations.
1030 : : *
1031 : : * We swap the physical identity (reltablespace, relfilenumber) while keeping
1032 : : * the same logical identities of the two relations. relpersistence is also
1033 : : * swapped, which is critical since it determines where buffers live for each
1034 : : * relation.
1035 : : *
1036 : : * We can swap associated TOAST data in either of two ways: recursively swap
1037 : : * the physical content of the toast tables (and their indexes), or swap the
1038 : : * TOAST links in the given relations' pg_class entries. The former is needed
1039 : : * to manage rewrites of shared catalogs (where we cannot change the pg_class
1040 : : * links) while the latter is the only way to handle cases in which a toast
1041 : : * table is added or removed altogether.
1042 : : *
1043 : : * Additionally, the first relation is marked with relfrozenxid set to
1044 : : * frozenXid. It seems a bit ugly to have this here, but the caller would
1045 : : * have to do it anyway, so having it here saves a heap_update. Note: in
1046 : : * the swap-toast-links case, we assume we don't need to change the toast
1047 : : * table's relfrozenxid: the new version of the toast table should already
1048 : : * have relfrozenxid set to RecentXmin, which is good enough.
1049 : : *
1050 : : * Lastly, if r2 and its toast table and toast index (if any) are mapped,
1051 : : * their OIDs are emitted into mapped_tables[]. This is hacky but beats
1052 : : * having to look the information up again later in finish_heap_swap.
1053 : : */
1054 : : static void
5180 tgl@sss.pgh.pa.us 1055 : 932 : swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
1056 : : bool swap_toast_by_content,
1057 : : bool is_internal,
1058 : : TransactionId frozenXid,
1059 : : MultiXactId cutoffMulti,
1060 : : Oid *mapped_tables)
1061 : : {
1062 : : Relation relRelation;
1063 : : HeapTuple reltup1,
1064 : : reltup2;
1065 : : Form_pg_class relform1,
1066 : : relform2;
1067 : : RelFileNumber relfilenumber1,
1068 : : relfilenumber2;
1069 : : RelFileNumber swaptemp;
1070 : : char swptmpchr;
1071 : : Oid relam1,
1072 : : relam2;
1073 : :
1074 : : /* We need writable copies of both pg_class tuples. */
1910 andres@anarazel.de 1075 : 932 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1076 : :
5173 rhaas@postgresql.org 1077 : 932 : reltup1 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r1));
7917 tgl@sss.pgh.pa.us 1078 [ - + ]: 932 : if (!HeapTupleIsValid(reltup1))
7574 tgl@sss.pgh.pa.us 1079 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", r1);
7917 tgl@sss.pgh.pa.us 1080 :CBC 932 : relform1 = (Form_pg_class) GETSTRUCT(reltup1);
1081 : :
5173 rhaas@postgresql.org 1082 : 932 : reltup2 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r2));
7917 tgl@sss.pgh.pa.us 1083 [ - + ]: 932 : if (!HeapTupleIsValid(reltup2))
7574 tgl@sss.pgh.pa.us 1084 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", r2);
7917 tgl@sss.pgh.pa.us 1085 :CBC 932 : relform2 = (Form_pg_class) GETSTRUCT(reltup2);
1086 : :
648 rhaas@postgresql.org 1087 : 932 : relfilenumber1 = relform1->relfilenode;
1088 : 932 : relfilenumber2 = relform2->relfilenode;
289 michael@paquier.xyz 1089 : 932 : relam1 = relform1->relam;
1090 : 932 : relam2 = relform2->relam;
1091 : :
648 rhaas@postgresql.org 1092 [ + + + - ]: 932 : if (RelFileNumberIsValid(relfilenumber1) &&
1093 : : RelFileNumberIsValid(relfilenumber2))
1094 : : {
1095 : : /*
1096 : : * Normal non-mapped relations: swap relfilenumbers, reltablespaces,
1097 : : * relpersistence
1098 : : */
5180 tgl@sss.pgh.pa.us 1099 [ - + ]: 851 : Assert(!target_is_pg_class);
1100 : :
1101 : 851 : swaptemp = relform1->relfilenode;
1102 : 851 : relform1->relfilenode = relform2->relfilenode;
1103 : 851 : relform2->relfilenode = swaptemp;
1104 : :
1105 : 851 : swaptemp = relform1->reltablespace;
1106 : 851 : relform1->reltablespace = relform2->reltablespace;
1107 : 851 : relform2->reltablespace = swaptemp;
1108 : :
991 michael@paquier.xyz 1109 : 851 : swaptemp = relform1->relam;
1110 : 851 : relform1->relam = relform2->relam;
1111 : 851 : relform2->relam = swaptemp;
1112 : :
3523 alvherre@alvh.no-ip. 1113 : 851 : swptmpchr = relform1->relpersistence;
1114 : 851 : relform1->relpersistence = relform2->relpersistence;
1115 : 851 : relform2->relpersistence = swptmpchr;
1116 : :
1117 : : /* Also swap toast links, if we're swapping by links */
5180 tgl@sss.pgh.pa.us 1118 [ + + ]: 851 : if (!swap_toast_by_content)
1119 : : {
1120 : 650 : swaptemp = relform1->reltoastrelid;
1121 : 650 : relform1->reltoastrelid = relform2->reltoastrelid;
1122 : 650 : relform2->reltoastrelid = swaptemp;
1123 : : }
1124 : : }
1125 : : else
1126 : : {
1127 : : /*
1128 : : * Mapped-relation case. Here we have to swap the relation mappings
1129 : : * instead of modifying the pg_class columns. Both must be mapped.
1130 : : */
648 rhaas@postgresql.org 1131 [ + - - + ]: 81 : if (RelFileNumberIsValid(relfilenumber1) ||
1132 : : RelFileNumberIsValid(relfilenumber2))
5180 tgl@sss.pgh.pa.us 1133 [ # # ]:UBC 0 : elog(ERROR, "cannot swap mapped relation \"%s\" with non-mapped relation",
1134 : : NameStr(relform1->relname));
1135 : :
1136 : : /*
1137 : : * We can't change the tablespace nor persistence of a mapped rel, and
1138 : : * we can't handle toast link swapping for one either, because we must
1139 : : * not apply any critical changes to its pg_class row. These cases
1140 : : * should be prevented by upstream permissions tests, so these checks
1141 : : * are non-user-facing emergency backstop.
1142 : : */
5180 tgl@sss.pgh.pa.us 1143 [ - + ]:CBC 81 : if (relform1->reltablespace != relform2->reltablespace)
5180 tgl@sss.pgh.pa.us 1144 [ # # ]:UBC 0 : elog(ERROR, "cannot change tablespace of mapped relation \"%s\"",
1145 : : NameStr(relform1->relname));
3523 alvherre@alvh.no-ip. 1146 [ - + ]:CBC 81 : if (relform1->relpersistence != relform2->relpersistence)
3523 alvherre@alvh.no-ip. 1147 [ # # ]:UBC 0 : elog(ERROR, "cannot change persistence of mapped relation \"%s\"",
1148 : : NameStr(relform1->relname));
991 michael@paquier.xyz 1149 [ - + ]:CBC 81 : if (relform1->relam != relform2->relam)
991 michael@paquier.xyz 1150 [ # # ]:UBC 0 : elog(ERROR, "cannot change access method of mapped relation \"%s\"",
1151 : : NameStr(relform1->relname));
5180 tgl@sss.pgh.pa.us 1152 [ + + ]:CBC 81 : if (!swap_toast_by_content &&
1153 [ + - - + ]: 15 : (relform1->reltoastrelid || relform2->reltoastrelid))
5180 tgl@sss.pgh.pa.us 1154 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast by links for mapped relation \"%s\"",
1155 : : NameStr(relform1->relname));
1156 : :
1157 : : /*
1158 : : * Fetch the mappings --- shouldn't fail, but be paranoid
1159 : : */
648 rhaas@postgresql.org 1160 :CBC 81 : relfilenumber1 = RelationMapOidToFilenumber(r1, relform1->relisshared);
1161 [ - + ]: 81 : if (!RelFileNumberIsValid(relfilenumber1))
5180 tgl@sss.pgh.pa.us 1162 [ # # ]:UBC 0 : elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
1163 : : NameStr(relform1->relname), r1);
648 rhaas@postgresql.org 1164 :CBC 81 : relfilenumber2 = RelationMapOidToFilenumber(r2, relform2->relisshared);
1165 [ - + ]: 81 : if (!RelFileNumberIsValid(relfilenumber2))
5180 tgl@sss.pgh.pa.us 1166 [ # # ]:UBC 0 : elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
1167 : : NameStr(relform2->relname), r2);
1168 : :
1169 : : /*
1170 : : * Send replacement mappings to relmapper. Note these won't actually
1171 : : * take effect until CommandCounterIncrement.
1172 : : */
648 rhaas@postgresql.org 1173 :CBC 81 : RelationMapUpdateMap(r1, relfilenumber2, relform1->relisshared, false);
1174 : 81 : RelationMapUpdateMap(r2, relfilenumber1, relform2->relisshared, false);
1175 : :
1176 : : /* Pass OIDs of mapped r2 tables back to caller */
5180 tgl@sss.pgh.pa.us 1177 : 81 : *mapped_tables++ = r2;
1178 : : }
1179 : :
1180 : : /*
1181 : : * Recognize that rel1's relfilenumber (swapped from rel2) is new in this
1182 : : * subtransaction. The rel2 storage (swapped from rel1) may or may not be
1183 : : * new.
1184 : : */
1185 : : {
1186 : : Relation rel1,
1187 : : rel2;
1188 : :
1471 noah@leadboat.com 1189 : 932 : rel1 = relation_open(r1, NoLock);
1190 : 932 : rel2 = relation_open(r2, NoLock);
1191 : 932 : rel2->rd_createSubid = rel1->rd_createSubid;
648 rhaas@postgresql.org 1192 : 932 : rel2->rd_newRelfilelocatorSubid = rel1->rd_newRelfilelocatorSubid;
1193 : 932 : rel2->rd_firstRelfilelocatorSubid = rel1->rd_firstRelfilelocatorSubid;
1194 : 932 : RelationAssumeNewRelfilelocator(rel1);
1471 noah@leadboat.com 1195 : 932 : relation_close(rel1, NoLock);
1196 : 932 : relation_close(rel2, NoLock);
1197 : : }
1198 : :
1199 : : /*
1200 : : * In the case of a shared catalog, these next few steps will only affect
1201 : : * our own database's pg_class row; but that's okay, because they are all
1202 : : * noncritical updates. That's also an important fact for the case of a
1203 : : * mapped catalog, because it's possible that we'll commit the map change
1204 : : * and then fail to commit the pg_class update.
1205 : : */
1206 : :
1207 : : /* set rel1's frozen Xid and minimum MultiXid */
5183 tgl@sss.pgh.pa.us 1208 [ + + ]: 932 : if (relform1->relkind != RELKIND_INDEX)
1209 : : {
1818 andres@anarazel.de 1210 [ + - - + ]: 843 : Assert(!TransactionIdIsValid(frozenXid) ||
1211 : : TransactionIdIsNormal(frozenXid));
5183 tgl@sss.pgh.pa.us 1212 : 843 : relform1->relfrozenxid = frozenXid;
3863 alvherre@alvh.no-ip. 1213 : 843 : relform1->relminmxid = cutoffMulti;
1214 : : }
1215 : :
1216 : : /* swap size statistics too, since new rel has freshly-updated stats */
1217 : : {
1218 : : int32 swap_pages;
1219 : : float4 swap_tuples;
1220 : : int32 swap_allvisible;
1221 : :
7834 tgl@sss.pgh.pa.us 1222 : 932 : swap_pages = relform1->relpages;
1223 : 932 : relform1->relpages = relform2->relpages;
1224 : 932 : relform2->relpages = swap_pages;
1225 : :
1226 : 932 : swap_tuples = relform1->reltuples;
1227 : 932 : relform1->reltuples = relform2->reltuples;
1228 : 932 : relform2->reltuples = swap_tuples;
1229 : :
4566 1230 : 932 : swap_allvisible = relform1->relallvisible;
1231 : 932 : relform1->relallvisible = relform2->relallvisible;
1232 : 932 : relform2->relallvisible = swap_allvisible;
1233 : : }
1234 : :
1235 : : /*
1236 : : * Update the tuples in pg_class --- unless the target relation of the
1237 : : * swap is pg_class itself. In that case, there is zero point in making
1238 : : * changes because we'd be updating the old data that we're about to throw
1239 : : * away. Because the real work being done here for a mapped relation is
1240 : : * just to change the relation map settings, it's all right to not update
1241 : : * the pg_class rows in this case. The most important changes will instead
1242 : : * performed later, in finish_heap_swap() itself.
1243 : : */
5180 1244 [ + + ]: 932 : if (!target_is_pg_class)
1245 : : {
1246 : : CatalogIndexState indstate;
1247 : :
1248 : 920 : indstate = CatalogOpenIndexes(relRelation);
2629 1249 : 920 : CatalogTupleUpdateWithInfo(relRelation, &reltup1->t_self, reltup1,
1250 : : indstate);
1251 : 920 : CatalogTupleUpdateWithInfo(relRelation, &reltup2->t_self, reltup2,
1252 : : indstate);
5180 1253 : 920 : CatalogCloseIndexes(indstate);
1254 : : }
1255 : : else
1256 : : {
1257 : : /* no update ... but we do still need relcache inval */
1258 : 12 : CacheInvalidateRelcacheByTuple(reltup1);
1259 : 12 : CacheInvalidateRelcacheByTuple(reltup2);
1260 : : }
1261 : :
1262 : : /*
1263 : : * Now that pg_class has been updated with its relevant information for
1264 : : * the swap, update the dependency of the relations to point to their new
1265 : : * table AM, if it has changed.
1266 : : */
289 michael@paquier.xyz 1267 [ + + ]: 932 : if (relam1 != relam2)
1268 : : {
1269 [ - + ]: 18 : if (changeDependencyFor(RelationRelationId,
1270 : : r1,
1271 : : AccessMethodRelationId,
1272 : : relam1,
1273 : : relam2) != 1)
279 michael@paquier.xyz 1274 [ # # ]:UNC 0 : elog(ERROR, "could not change access method dependency for relation \"%s.%s\"",
1275 : : get_namespace_name(get_rel_namespace(r1)),
1276 : : get_rel_name(r1));
289 michael@paquier.xyz 1277 [ - + ]:CBC 18 : if (changeDependencyFor(RelationRelationId,
1278 : : r2,
1279 : : AccessMethodRelationId,
1280 : : relam2,
1281 : : relam1) != 1)
279 michael@paquier.xyz 1282 [ # # ]:UNC 0 : elog(ERROR, "could not change access method dependency for relation \"%s.%s\"",
1283 : : get_namespace_name(get_rel_namespace(r2)),
1284 : : get_rel_name(r2));
1285 : : }
1286 : :
1287 : : /*
1288 : : * Post alter hook for modified relations. The change to r2 is always
1289 : : * internal, but r1 depends on the invocation context.
1290 : : */
4046 rhaas@postgresql.org 1291 [ - + ]:CBC 932 : InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
1292 : : InvalidOid, is_internal);
1293 [ - + ]: 932 : InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
1294 : : InvalidOid, true);
1295 : :
1296 : : /*
1297 : : * If we have toast tables associated with the relations being swapped,
1298 : : * deal with them too.
1299 : : */
7917 tgl@sss.pgh.pa.us 1300 [ + + + + ]: 932 : if (relform1->reltoastrelid || relform2->reltoastrelid)
1301 : : {
5183 1302 [ + + ]: 311 : if (swap_toast_by_content)
1303 : : {
1304 [ + - + - ]: 89 : if (relform1->reltoastrelid && relform2->reltoastrelid)
1305 : : {
1306 : : /* Recursively swap the contents of the toast tables */
1307 : 89 : swap_relation_files(relform1->reltoastrelid,
1308 : : relform2->reltoastrelid,
1309 : : target_is_pg_class,
1310 : : swap_toast_by_content,
1311 : : is_internal,
1312 : : frozenXid,
1313 : : cutoffMulti,
1314 : : mapped_tables);
1315 : : }
1316 : : else
1317 : : {
1318 : : /* caller messed up */
5183 tgl@sss.pgh.pa.us 1319 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast files by content when there's only one");
1320 : : }
1321 : : }
1322 : : else
1323 : : {
1324 : : /*
1325 : : * We swapped the ownership links, so we need to change dependency
1326 : : * data to match.
1327 : : *
1328 : : * NOTE: it is possible that only one table has a toast table.
1329 : : *
1330 : : * NOTE: at present, a TOAST table's only dependency is the one on
1331 : : * its owning table. If more are ever created, we'd need to use
1332 : : * something more selective than deleteDependencyRecordsFor() to
1333 : : * get rid of just the link we want.
1334 : : */
1335 : : ObjectAddress baseobject,
1336 : : toastobject;
1337 : : long count;
1338 : :
1339 : : /*
1340 : : * We disallow this case for system catalogs, to avoid the
1341 : : * possibility that the catalog we're rebuilding is one of the
1342 : : * ones the dependency changes would change. It's too late to be
1343 : : * making any data changes to the target catalog.
1344 : : */
3790 rhaas@postgresql.org 1345 [ - + ]:CBC 222 : if (IsSystemClass(r1, relform1))
5180 tgl@sss.pgh.pa.us 1346 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast files by links for system catalogs");
1347 : :
1348 : : /* Delete old dependencies */
5183 tgl@sss.pgh.pa.us 1349 [ + + ]:CBC 222 : if (relform1->reltoastrelid)
1350 : : {
1351 : 206 : count = deleteDependencyRecordsFor(RelationRelationId,
1352 : : relform1->reltoastrelid,
1353 : : false);
1354 [ - + ]: 206 : if (count != 1)
5183 tgl@sss.pgh.pa.us 1355 [ # # ]:UBC 0 : elog(ERROR, "expected one dependency record for TOAST table, found %ld",
1356 : : count);
1357 : : }
5183 tgl@sss.pgh.pa.us 1358 [ + - ]:CBC 222 : if (relform2->reltoastrelid)
1359 : : {
1360 : 222 : count = deleteDependencyRecordsFor(RelationRelationId,
1361 : : relform2->reltoastrelid,
1362 : : false);
1363 [ - + ]: 222 : if (count != 1)
5183 tgl@sss.pgh.pa.us 1364 [ # # ]:UBC 0 : elog(ERROR, "expected one dependency record for TOAST table, found %ld",
1365 : : count);
1366 : : }
1367 : :
1368 : : /* Register new dependencies */
5183 tgl@sss.pgh.pa.us 1369 :CBC 222 : baseobject.classId = RelationRelationId;
1370 : 222 : baseobject.objectSubId = 0;
1371 : 222 : toastobject.classId = RelationRelationId;
1372 : 222 : toastobject.objectSubId = 0;
1373 : :
1374 [ + + ]: 222 : if (relform1->reltoastrelid)
1375 : : {
1376 : 206 : baseobject.objectId = r1;
1377 : 206 : toastobject.objectId = relform1->reltoastrelid;
1378 : 206 : recordDependencyOn(&toastobject, &baseobject,
1379 : : DEPENDENCY_INTERNAL);
1380 : : }
1381 : :
1382 [ + - ]: 222 : if (relform2->reltoastrelid)
1383 : : {
1384 : 222 : baseobject.objectId = r2;
1385 : 222 : toastobject.objectId = relform2->reltoastrelid;
1386 : 222 : recordDependencyOn(&toastobject, &baseobject,
1387 : : DEPENDENCY_INTERNAL);
1388 : : }
1389 : : }
1390 : : }
1391 : :
1392 : : /*
1393 : : * If we're swapping two toast tables by content, do the same for their
1394 : : * valid index. The swap can actually be safely done only if the relations
1395 : : * have indexes.
1396 : : */
1397 [ + + ]: 932 : if (swap_toast_by_content &&
3937 fujii@postgresql.org 1398 [ + + ]: 267 : relform1->relkind == RELKIND_TOASTVALUE &&
1399 [ + - ]: 89 : relform2->relkind == RELKIND_TOASTVALUE)
1400 : : {
1401 : : Oid toastIndex1,
1402 : : toastIndex2;
1403 : :
1404 : : /* Get valid index for each relation */
1405 : 89 : toastIndex1 = toast_get_valid_index(r1,
1406 : : AccessExclusiveLock);
1407 : 89 : toastIndex2 = toast_get_valid_index(r2,
1408 : : AccessExclusiveLock);
1409 : :
1410 : 89 : swap_relation_files(toastIndex1,
1411 : : toastIndex2,
1412 : : target_is_pg_class,
1413 : : swap_toast_by_content,
1414 : : is_internal,
1415 : : InvalidTransactionId,
1416 : : InvalidMultiXactId,
1417 : : mapped_tables);
1418 : : }
1419 : :
1420 : : /* Clean up. */
7917 tgl@sss.pgh.pa.us 1421 : 932 : heap_freetuple(reltup1);
1422 : 932 : heap_freetuple(reltup2);
1423 : :
1910 andres@anarazel.de 1424 : 932 : table_close(relRelation, RowExclusiveLock);
7918 bruce@momjian.us 1425 : 932 : }
1426 : :
1427 : : /*
1428 : : * Remove the transient table that was built by make_new_heap, and finish
1429 : : * cleaning up (including rebuilding all indexes on the old heap).
1430 : : */
1431 : : void
5180 tgl@sss.pgh.pa.us 1432 : 754 : finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
1433 : : bool is_system_catalog,
1434 : : bool swap_toast_by_content,
1435 : : bool check_constraints,
1436 : : bool is_internal,
1437 : : TransactionId frozenXid,
1438 : : MultiXactId cutoffMulti,
1439 : : char newrelpersistence)
1440 : : {
1441 : : ObjectAddress object;
1442 : : Oid mapped_tables[4];
1443 : : int reindex_flags;
1182 michael@paquier.xyz 1444 : 754 : ReindexParams reindex_params = {0};
1445 : : int i;
1446 : :
1447 : : /* Report that we are now swapping relation files */
1847 rhaas@postgresql.org 1448 : 754 : pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
1449 : : PROGRESS_CLUSTER_PHASE_SWAP_REL_FILES);
1450 : :
1451 : : /* Zero out possible results from swapped_relation_files */
5180 tgl@sss.pgh.pa.us 1452 : 754 : memset(mapped_tables, 0, sizeof(mapped_tables));
1453 : :
1454 : : /*
1455 : : * Swap the contents of the heap relations (including any toast tables).
1456 : : * Also set old heap's relfrozenxid to frozenXid.
1457 : : */
1458 : 754 : swap_relation_files(OIDOldHeap, OIDNewHeap,
1459 : : (OIDOldHeap == RelationRelationId),
1460 : : swap_toast_by_content, is_internal,
1461 : : frozenXid, cutoffMulti, mapped_tables);
1462 : :
1463 : : /*
1464 : : * If it's a system catalog, queue a sinval message to flush all catcaches
1465 : : * on the catalog when we reach CommandCounterIncrement.
1466 : : */
1467 [ + + ]: 754 : if (is_system_catalog)
1468 : 101 : CacheInvalidateCatalog(OIDOldHeap);
1469 : :
1470 : : /*
1471 : : * Rebuild each index on the relation (but not the toast table, which is
1472 : : * all-new at this point). It is important to do this before the DROP
1473 : : * step because if we are processing a system catalog that will be used
1474 : : * during DROP, we want to have its indexes available. There is no
1475 : : * advantage to the other order anyway because this is all transactional,
1476 : : * so no chance to reclaim disk space before commit. We do not need a
1477 : : * final CommandCounterIncrement() because reindex_relation does it.
1478 : : *
1479 : : * Note: because index_build is called via reindex_relation, it will never
1480 : : * set indcheckxmin true for the indexes. This is OK even though in some
1481 : : * sense we are building new indexes rather than rebuilding existing ones,
1482 : : * because the new heap won't contain any HOT chains at all, let alone
1483 : : * broken ones, so it can't be necessary to set indcheckxmin.
1484 : : */
4747 1485 : 754 : reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
4833 rhaas@postgresql.org 1486 [ + + ]: 754 : if (check_constraints)
4747 tgl@sss.pgh.pa.us 1487 : 491 : reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
1488 : :
1489 : : /*
1490 : : * Ensure that the indexes have the same persistence as the parent
1491 : : * relation.
1492 : : */
3438 alvherre@alvh.no-ip. 1493 [ + + ]: 754 : if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
1494 : 19 : reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
1495 [ + + ]: 735 : else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
1496 : 698 : reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
1497 : :
1498 : : /* Report that we are now reindexing relations */
1847 rhaas@postgresql.org 1499 : 754 : pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
1500 : : PROGRESS_CLUSTER_PHASE_REBUILD_INDEX);
1501 : :
132 michael@paquier.xyz 1502 :GNC 754 : reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
1503 : :
1504 : : /* Report that we are now doing clean up */
1847 rhaas@postgresql.org 1505 :CBC 745 : pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE,
1506 : : PROGRESS_CLUSTER_PHASE_FINAL_CLEANUP);
1507 : :
1508 : : /*
1509 : : * If the relation being rebuilt is pg_class, swap_relation_files()
1510 : : * couldn't update pg_class's own pg_class entry (check comments in
1511 : : * swap_relation_files()), thus relfrozenxid was not updated. That's
1512 : : * annoying because a potential reason for doing a VACUUM FULL is a
1513 : : * imminent or actual anti-wraparound shutdown. So, now that we can
1514 : : * access the new relation using its indices, update relfrozenxid.
1515 : : * pg_class doesn't have a toast relation, so we don't need to update the
1516 : : * corresponding toast relation. Not that there's little point moving all
1517 : : * relfrozenxid updates here since swap_relation_files() needs to write to
1518 : : * pg_class for non-mapped relations anyway.
1519 : : */
3694 1520 [ + + ]: 745 : if (OIDOldHeap == RelationRelationId)
1521 : : {
1522 : : Relation relRelation;
1523 : : HeapTuple reltup;
1524 : : Form_pg_class relform;
1525 : :
1910 andres@anarazel.de 1526 : 12 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1527 : :
3694 rhaas@postgresql.org 1528 : 12 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(OIDOldHeap));
1529 [ - + ]: 12 : if (!HeapTupleIsValid(reltup))
3694 rhaas@postgresql.org 1530 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", OIDOldHeap);
3694 rhaas@postgresql.org 1531 :CBC 12 : relform = (Form_pg_class) GETSTRUCT(reltup);
1532 : :
1533 : 12 : relform->relfrozenxid = frozenXid;
1534 : 12 : relform->relminmxid = cutoffMulti;
1535 : :
2630 alvherre@alvh.no-ip. 1536 : 12 : CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
1537 : :
1910 andres@anarazel.de 1538 : 12 : table_close(relRelation, RowExclusiveLock);
1539 : : }
1540 : :
1541 : : /* Destroy new heap with old filenumber */
5183 tgl@sss.pgh.pa.us 1542 : 745 : object.classId = RelationRelationId;
1543 : 745 : object.objectId = OIDNewHeap;
1544 : 745 : object.objectSubId = 0;
1545 : :
1546 : : /*
1547 : : * The new relation is local to our transaction and we know nothing
1548 : : * depends on it, so DROP_RESTRICT should be OK.
1549 : : */
4462 rhaas@postgresql.org 1550 : 745 : performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
1551 : :
1552 : : /* performDeletion does CommandCounterIncrement at end */
1553 : :
1554 : : /*
1555 : : * Now we must remove any relation mapping entries that we set up for the
1556 : : * transient table, as well as its toast table and toast index if any. If
1557 : : * we fail to do this before commit, the relmapper will complain about new
1558 : : * permanent map entries being added post-bootstrap.
1559 : : */
5180 tgl@sss.pgh.pa.us 1560 [ + + ]: 826 : for (i = 0; OidIsValid(mapped_tables[i]); i++)
1561 : 81 : RelationMapRemoveMapping(mapped_tables[i]);
1562 : :
1563 : : /*
1564 : : * At this point, everything is kosher except that, if we did toast swap
1565 : : * by links, the toast table's name corresponds to the transient table.
1566 : : * The name is irrelevant to the backend because it's referenced by OID,
1567 : : * but users looking at the catalogs could be confused. Rename it to
1568 : : * prevent this problem.
1569 : : *
1570 : : * Note no lock required on the relation, because we already hold an
1571 : : * exclusive lock on it.
1572 : : */
5183 1573 [ + + ]: 745 : if (!swap_toast_by_content)
1574 : : {
1575 : : Relation newrel;
1576 : :
1910 andres@anarazel.de 1577 : 656 : newrel = table_open(OIDOldHeap, NoLock);
5183 tgl@sss.pgh.pa.us 1578 [ + + ]: 656 : if (OidIsValid(newrel->rd_rel->reltoastrelid))
1579 : : {
1580 : : Oid toastidx;
1581 : : char NewToastName[NAMEDATALEN];
1582 : :
1583 : : /* Get the associated valid index to be renamed */
3937 fujii@postgresql.org 1584 : 206 : toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid,
1585 : : NoLock);
1586 : :
1587 : : /* rename the toast table ... */
5183 tgl@sss.pgh.pa.us 1588 : 206 : snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
1589 : : OIDOldHeap);
1590 : 206 : RenameRelationInternal(newrel->rd_rel->reltoastrelid,
1591 : : NewToastName, true, false);
1592 : :
1593 : : /* ... and its valid index too. */
1594 : 206 : snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
1595 : : OIDOldHeap);
1596 : :
1597 : 206 : RenameRelationInternal(toastidx,
1598 : : NewToastName, true, true);
1599 : :
1600 : : /*
1601 : : * Reset the relrewrite for the toast. The command-counter
1602 : : * increment is required here as we are about to update the tuple
1603 : : * that is updated as part of RenameRelationInternal.
1604 : : */
963 akapila@postgresql.o 1605 : 206 : CommandCounterIncrement();
1606 : 206 : ResetRelRewrite(newrel->rd_rel->reltoastrelid);
1607 : : }
5183 tgl@sss.pgh.pa.us 1608 : 656 : relation_close(newrel, NoLock);
1609 : : }
1610 : :
1611 : : /* if it's not a catalog table, clear any missing attribute settings */
2209 andrew@dunslane.net 1612 [ + + ]: 745 : if (!is_system_catalog)
1613 : : {
1614 : : Relation newrel;
1615 : :
1910 andres@anarazel.de 1616 : 644 : newrel = table_open(OIDOldHeap, NoLock);
2209 andrew@dunslane.net 1617 : 644 : RelationClearMissing(newrel);
1618 : 644 : relation_close(newrel, NoLock);
1619 : : }
5183 tgl@sss.pgh.pa.us 1620 : 745 : }
1621 : :
1622 : :
1623 : : /*
1624 : : * Get a list of tables that the current user has privileges on and
1625 : : * have indisclustered set. Return the list in a List * of RelToCluster
1626 : : * (stored in the specified memory context), each one giving the tableOid
1627 : : * and the indexOid on which the table is already clustered.
1628 : : */
1629 : : static List *
7776 1630 : 10 : get_tables_to_cluster(MemoryContext cluster_context)
1631 : : {
1632 : : Relation indRelation;
1633 : : TableScanDesc scan;
1634 : : ScanKeyData entry;
1635 : : HeapTuple indexTuple;
1636 : : Form_pg_index index;
1637 : : MemoryContext old_context;
743 alvherre@alvh.no-ip. 1638 : 10 : List *rtcs = NIL;
1639 : :
1640 : : /*
1641 : : * Get all indexes that have indisclustered set and that the current user
1642 : : * has the appropriate privileges for.
1643 : : */
1910 andres@anarazel.de 1644 : 10 : indRelation = table_open(IndexRelationId, AccessShareLock);
7459 tgl@sss.pgh.pa.us 1645 : 10 : ScanKeyInit(&entry,
1646 : : Anum_pg_index_indisclustered,
1647 : : BTEqualStrategyNumber, F_BOOLEQ,
1648 : : BoolGetDatum(true));
1861 andres@anarazel.de 1649 : 10 : scan = table_beginscan_catalog(indRelation, 1, &entry);
7821 bruce@momjian.us 1650 [ + + ]: 19 : while ((indexTuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
1651 : : {
1652 : : RelToCluster *rtc;
1653 : :
1654 : 9 : index = (Form_pg_index) GETSTRUCT(indexTuple);
1655 : :
32 nathan@postgresql.or 1656 [ + + ]:GNC 9 : if (!cluster_is_permitted_for_relation(index->indrelid, GetUserId()))
7821 bruce@momjian.us 1657 :CBC 6 : continue;
1658 : :
1659 : : /* Use a permanent memory context for the result list */
1660 : 3 : old_context = MemoryContextSwitchTo(cluster_context);
1661 : :
743 alvherre@alvh.no-ip. 1662 : 3 : rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
1663 : 3 : rtc->tableOid = index->indrelid;
1664 : 3 : rtc->indexOid = index->indexrelid;
1665 : 3 : rtcs = lappend(rtcs, rtc);
1666 : :
7821 bruce@momjian.us 1667 : 3 : MemoryContextSwitchTo(old_context);
1668 : : }
1861 andres@anarazel.de 1669 : 10 : table_endscan(scan);
1670 : :
7776 tgl@sss.pgh.pa.us 1671 : 10 : relation_close(indRelation, AccessShareLock);
1672 : :
743 alvherre@alvh.no-ip. 1673 : 10 : return rtcs;
1674 : : }
1675 : :
1676 : : /*
1677 : : * Given an index on a partitioned table, return a list of RelToCluster for
1678 : : * all the children leaves tables/indexes.
1679 : : *
1680 : : * Like expand_vacuum_rel, but here caller must hold AccessExclusiveLock
1681 : : * on the table containing the index.
1682 : : */
1683 : : static List *
1684 : 10 : get_tables_to_cluster_partitioned(MemoryContext cluster_context, Oid indexOid)
1685 : : {
1686 : : List *inhoids;
1687 : : ListCell *lc;
1688 : 10 : List *rtcs = NIL;
1689 : : MemoryContext old_context;
1690 : :
1691 : : /* Do not lock the children until they're processed */
1692 : 10 : inhoids = find_all_inheritors(indexOid, NoLock, NULL);
1693 : :
1694 [ + - + + : 52 : foreach(lc, inhoids)
+ + ]
1695 : : {
1696 : 42 : Oid indexrelid = lfirst_oid(lc);
1697 : 42 : Oid relid = IndexGetRelation(indexrelid, false);
1698 : : RelToCluster *rtc;
1699 : :
1700 : : /* consider only leaf indexes */
1701 [ + + ]: 42 : if (get_rel_relkind(indexrelid) != RELKIND_INDEX)
1702 : 19 : continue;
1703 : :
1704 : : /*
1705 : : * It's possible that the user does not have privileges to CLUSTER the
1706 : : * leaf partition despite having such privileges on the partitioned
1707 : : * table. We skip any partitions which the user is not permitted to
1708 : : * CLUSTER.
1709 : : */
32 nathan@postgresql.or 1710 [ + + ]:GNC 23 : if (!cluster_is_permitted_for_relation(relid, GetUserId()))
297 nathan@postgresql.or 1711 :CBC 11 : continue;
1712 : :
1713 : : /* Use a permanent memory context for the result list */
731 alvherre@alvh.no-ip. 1714 : 12 : old_context = MemoryContextSwitchTo(cluster_context);
1715 : :
743 1716 : 12 : rtc = (RelToCluster *) palloc(sizeof(RelToCluster));
1717 : 12 : rtc->tableOid = relid;
1718 : 12 : rtc->indexOid = indexrelid;
1719 : 12 : rtcs = lappend(rtcs, rtc);
1720 : :
731 1721 : 12 : MemoryContextSwitchTo(old_context);
1722 : : }
1723 : :
743 1724 : 10 : return rtcs;
1725 : : }
1726 : :
1727 : : /*
1728 : : * Return whether userid has privileges to CLUSTER relid. If not, this
1729 : : * function emits a WARNING.
1730 : : */
1731 : : static bool
32 nathan@postgresql.or 1732 :GNC 47 : cluster_is_permitted_for_relation(Oid relid, Oid userid)
1733 : : {
1734 [ + + ]: 47 : if (pg_class_aclcheck(relid, userid, ACL_MAINTAIN) == ACLCHECK_OK)
1735 : 30 : return true;
1736 : :
1737 [ + - ]: 17 : ereport(WARNING,
1738 : : (errmsg("permission denied to cluster \"%s\", skipping it",
1739 : : get_rel_name(relid))));
1740 : 17 : return false;
1741 : : }
|