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