Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * repack.c
4 : : * REPACK a table; formerly known as CLUSTER. VACUUM FULL also uses
5 : : * parts of this code.
6 : : *
7 : : * There are two somewhat different ways to rewrite a table. In non-
8 : : * concurrent mode, it's easy: take AccessExclusiveLock, create a new
9 : : * transient relation, copy the tuples over to the relfilenode of the new
10 : : * relation, swap the relfilenodes, then drop the old relation.
11 : : *
12 : : * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock,
13 : : * then do an initial copy as above. However, while the tuples are being
14 : : * copied, concurrent transactions could modify the table. To cope with those
15 : : * changes, we rely on logical decoding to obtain them from WAL. A bgworker
16 : : * consumes WAL while the initial copy is ongoing (to prevent excessive WAL
17 : : * from being reserved), and accumulates the changes in a file. Once the
18 : : * initial copy is complete, we read the changes from the file and re-apply
19 : : * them on the new heap. Then we upgrade our ShareUpdateExclusiveLock to
20 : : * AccessExclusiveLock and swap the relfilenodes. This way, the time we hold
21 : : * a strong lock on the table is much reduced, and the bloat is eliminated.
22 : : *
23 : : *
24 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
25 : : * Portions Copyright (c) 1994-5, Regents of the University of California
26 : : *
27 : : *
28 : : * IDENTIFICATION
29 : : * src/backend/commands/repack.c
30 : : *
31 : : *-------------------------------------------------------------------------
32 : : */
33 : : #include "postgres.h"
34 : :
35 : : #include "access/amapi.h"
36 : : #include "access/heapam.h"
37 : : #include "access/multixact.h"
38 : : #include "access/relscan.h"
39 : : #include "access/tableam.h"
40 : : #include "access/toast_internals.h"
41 : : #include "access/transam.h"
42 : : #include "access/xact.h"
43 : : #include "access/xlog.h"
44 : : #include "catalog/catalog.h"
45 : : #include "catalog/dependency.h"
46 : : #include "catalog/heap.h"
47 : : #include "catalog/index.h"
48 : : #include "catalog/namespace.h"
49 : : #include "catalog/objectaccess.h"
50 : : #include "catalog/pg_am.h"
51 : : #include "catalog/pg_attrdef.h"
52 : : #include "catalog/pg_constraint.h"
53 : : #include "catalog/pg_inherits.h"
54 : : #include "catalog/toasting.h"
55 : : #include "commands/defrem.h"
56 : : #include "commands/progress.h"
57 : : #include "commands/repack.h"
58 : : #include "commands/repack_internal.h"
59 : : #include "commands/tablecmds.h"
60 : : #include "commands/vacuum.h"
61 : : #include "executor/executor.h"
62 : : #include "libpq/pqformat.h"
63 : : #include "libpq/pqmq.h"
64 : : #include "miscadmin.h"
65 : : #include "optimizer/optimizer.h"
66 : : #include "parser/parse_relation.h"
67 : : #include "pgstat.h"
68 : : #include "replication/logicalrelation.h"
69 : : #include "storage/bufmgr.h"
70 : : #include "storage/ipc.h"
71 : : #include "storage/lmgr.h"
72 : : #include "storage/predicate.h"
73 : : #include "storage/proc.h"
74 : : #include "utils/acl.h"
75 : : #include "utils/fmgroids.h"
76 : : #include "utils/guc.h"
77 : : #include "utils/injection_point.h"
78 : : #include "utils/inval.h"
79 : : #include "utils/lsyscache.h"
80 : : #include "utils/memutils.h"
81 : : #include "utils/pg_rusage.h"
82 : : #include "utils/relmapper.h"
83 : : #include "utils/snapmgr.h"
84 : : #include "utils/syscache.h"
85 : : #include "utils/wait_event_types.h"
86 : :
87 : : /*
88 : : * This struct is used to pass around the information on tables to be
89 : : * clustered. We need this so we can make a list of them when invoked without
90 : : * a specific table/index pair.
91 : : */
92 : : typedef struct
93 : : {
94 : : Oid tableOid;
95 : : Oid indexOid;
96 : : } RelToCluster;
97 : :
98 : : /*
99 : : * The first file exported by the decoding worker must contain a snapshot, the
100 : : * following ones contain the data changes.
101 : : */
102 : : #define WORKER_FILE_SNAPSHOT 0
103 : :
104 : : /*
105 : : * Information needed to apply concurrent data changes.
106 : : */
107 : : typedef struct ChangeContext
108 : : {
109 : : /* The relation the changes are applied to. */
110 : : Relation cc_rel;
111 : :
112 : : /* Needed to update indexes of cc_rel. */
113 : : ResultRelInfo *cc_rri;
114 : : EState *cc_estate;
115 : :
116 : : /*
117 : : * Existing tuples to UPDATE and DELETE are located via this index. We
118 : : * keep the scankey in partially initialized state to avoid repeated work.
119 : : * sk_argument is completed on the fly.
120 : : */
121 : : Relation cc_ident_index;
122 : : ScanKey cc_ident_key;
123 : : int cc_ident_key_nentries;
124 : :
125 : : /* The latest column we need to deform to have the tuple identity */
126 : : AttrNumber cc_last_key_attno;
127 : :
128 : : /* Sequential number of the file containing the changes. */
129 : : int cc_file_seq;
130 : : } ChangeContext;
131 : :
132 : : /*
133 : : * Backend-local information to control the decoding worker.
134 : : */
135 : : typedef struct DecodingWorker
136 : : {
137 : : /* The worker. */
138 : : BackgroundWorkerHandle *handle;
139 : :
140 : : /* DecodingWorkerShared is in this segment. */
141 : : dsm_segment *seg;
142 : :
143 : : /* Handle of the error queue. */
144 : : shm_mq_handle *error_mqh;
145 : : } DecodingWorker;
146 : :
147 : : /* Pointer to currently running decoding worker. */
148 : : static DecodingWorker *decoding_worker = NULL;
149 : :
150 : : /*
151 : : * Is there a message sent by a repack worker that the backend needs to
152 : : * receive?
153 : : */
154 : : volatile sig_atomic_t RepackMessagePending = false;
155 : :
156 : : static LOCKMODE RepackLockLevel(bool concurrent);
157 : : static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
158 : : Oid indexOid, Oid userid, LOCKMODE lmode,
159 : : int options);
160 : : static void check_concurrent_repack_requirements(Relation rel,
161 : : Oid *ident_idx_p);
162 : : static void rebuild_relation(Relation OldHeap, Relation index, bool verbose,
163 : : Oid ident_idx);
164 : : static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
165 : : Snapshot snapshot,
166 : : bool verbose,
167 : : bool *pSwapToastByContent,
168 : : TransactionId *pFreezeXid,
169 : : MultiXactId *pCutoffMulti);
170 : : static List *get_tables_to_repack(RepackCommand cmd, bool usingindex,
171 : : MemoryContext permcxt);
172 : : static List *get_tables_to_repack_partitioned(RepackStmt *stmt,
173 : : Relation rel,
174 : : MemoryContext permcxt);
175 : : static bool repack_is_permitted_for_relation(RepackCommand cmd,
176 : : Oid relid, Oid userid,
177 : : bool already_locked);
178 : :
179 : : static void apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt);
180 : : static void apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
181 : : ChangeContext *chgcxt);
182 : : static void apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
183 : : TupleTableSlot *ondisk_tuple,
184 : : ChangeContext *chgcxt);
185 : : static void apply_concurrent_delete(Relation rel, TupleTableSlot *slot);
186 : : static void restore_tuple(BufFile *file, Relation relation,
187 : : TupleTableSlot *slot);
188 : : static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest,
189 : : TupleTableSlot *src);
190 : : static bool find_target_tuple(Relation rel, ChangeContext *chgcxt,
191 : : TupleTableSlot *locator,
192 : : TupleTableSlot *retrieved);
193 : : static bool identity_key_equal(ChangeContext *chgcxt,
194 : : TupleTableSlot *locator,
195 : : TupleTableSlot *candidate);
196 : : static void process_concurrent_changes(XLogRecPtr end_of_wal,
197 : : ChangeContext *chgcxt,
198 : : bool done);
199 : : static void initialize_change_context(ChangeContext *chgcxt,
200 : : Relation relation,
201 : : Oid ident_index_id);
202 : : static void release_change_context(ChangeContext *chgcxt);
203 : : static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
204 : : Oid identIdx,
205 : : TransactionId frozenXid,
206 : : MultiXactId cutoffMulti);
207 : : static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes);
208 : : static void copy_index_constraints(Relation old_index, Oid new_index_id,
209 : : Oid new_heap_id);
210 : : static void copy_attribute_defaults(Oid old_heap_oid, Oid new_heap_oid);
211 : : static Relation process_single_relation(RepackStmt *stmt,
212 : : LOCKMODE lockmode,
213 : : bool isTopLevel,
214 : : ClusterParams *params);
215 : : static Oid determine_clustered_index(Relation rel, bool usingindex,
216 : : const char *indexname);
217 : :
218 : : static void start_repack_decoding_worker(Oid relid);
219 : : static void stop_repack_decoding_worker(void);
220 : : static void stop_repack_decoding_worker_cb(int code, Datum arg);
221 : : static Snapshot get_initial_snapshot(DecodingWorker *worker);
222 : :
223 : : static void ProcessRepackMessage(StringInfo msg);
224 : : static const char *RepackCommandAsString(RepackCommand cmd);
225 : :
226 : :
227 : : /*
228 : : * The repack code allows for processing multiple tables at once. Because
229 : : * of this, we cannot just run everything on a single transaction, or we
230 : : * would be forced to acquire exclusive locks on all the tables being
231 : : * clustered, simultaneously --- very likely leading to deadlock.
232 : : *
233 : : * To solve this we follow a similar strategy to VACUUM code, processing each
234 : : * relation in a separate transaction. For this to work, we need to:
235 : : *
236 : : * - provide a separate memory context so that we can pass information in
237 : : * a way that survives across transactions
238 : : * - start a new transaction every time a new relation is clustered
239 : : * - check for validity of the information on to-be-clustered relations,
240 : : * as someone might have deleted a relation behind our back, or
241 : : * clustered one on a different index
242 : : * - end the transaction
243 : : *
244 : : * The single-relation case does not have any such overhead.
245 : : *
246 : : * We also allow a relation to be repacked following an index, but without
247 : : * naming a specific one. In that case, the indisclustered bit will be
248 : : * looked up, and an ERROR will be thrown if no so-marked index is found.
249 : : */
250 : : void
170 alvherre@kurilemu.de 251 :CBC 232 : ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
252 : : {
2047 michael@paquier.xyz 253 : 232 : ClusterParams params = {0};
1608 alvherre@alvh.no-ip. 254 : 232 : Relation rel = NULL;
255 : : MemoryContext repack_context;
256 : : LOCKMODE lockmode;
257 : : List *rtcs;
101 fujii@postgresql.org 258 : 232 : bool verbose = false;
259 : 232 : bool analyze = false;
260 : 232 : bool concurrently = false;
261 : :
262 : : /* Parse option list */
170 alvherre@kurilemu.de 263 [ + + + + : 494 : foreach_node(DefElem, opt, stmt->params)
+ + ]
264 : : {
2093 michael@paquier.xyz 265 [ + + ]: 30 : if (strcmp(opt->defname, "verbose") == 0)
101 fujii@postgresql.org 266 : 7 : verbose = defGetBoolean(opt);
170 alvherre@kurilemu.de 267 [ + + ]: 23 : else if (strcmp(opt->defname, "analyze") == 0 ||
268 [ - + ]: 15 : strcmp(opt->defname, "analyse") == 0)
269 : : {
14 nathan@postgresql.or 270 [ - + ]: 8 : if (stmt->command != REPACK_COMMAND_REPACK)
14 nathan@postgresql.or 271 [ # # ]:UBC 0 : ereport(ERROR,
272 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
273 : : errmsg("ANALYZE option not supported for %s",
274 : : RepackCommandAsString(stmt->command)));
101 fujii@postgresql.org 275 :CBC 8 : analyze = defGetBoolean(opt);
276 : : }
277 [ + - ]: 15 : else if (strcmp(opt->defname, "concurrently") == 0)
278 : : {
143 alvherre@kurilemu.de 279 [ - + ]: 15 : if (stmt->command != REPACK_COMMAND_REPACK)
143 alvherre@kurilemu.de 280 [ # # ]:UBC 0 : ereport(ERROR,
281 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
282 : : errmsg("CONCURRENTLY option not supported for %s",
283 : : RepackCommandAsString(stmt->command)));
101 fujii@postgresql.org 284 :CBC 15 : concurrently = defGetBoolean(opt);
285 : : }
286 : : else
2093 michael@paquier.xyz 287 [ # # ]:UBC 0 : ereport(ERROR,
288 : : errcode(ERRCODE_SYNTAX_ERROR),
289 : : errmsg("unrecognized %s option \"%s\"",
290 : : RepackCommandAsString(stmt->command),
291 : : opt->defname),
292 : : parser_errposition(pstate, opt->location));
293 : : }
294 : :
101 fujii@postgresql.org 295 :CBC 464 : params.options |=
296 : 464 : (verbose ? CLUOPT_VERBOSE : 0) |
297 [ + + ]: 232 : (analyze ? CLUOPT_ANALYZE : 0) |
298 [ + + ]: 232 : (concurrently ? CLUOPT_CONCURRENT : 0);
299 : :
300 : : /* Determine the lock mode to use. */
143 alvherre@kurilemu.de 301 : 232 : lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
302 : :
303 [ + + ]: 232 : if ((params.options & CLUOPT_CONCURRENT) != 0)
304 : : {
305 : : /*
306 : : * Make sure we're not in a transaction block.
307 : : *
308 : : * The reason is that repack_setup_logical_decoding() could wait
309 : : * indefinitely for our XID to complete. (The deadlock detector would
310 : : * not recognize it because we'd be waiting for ourselves, i.e. no
311 : : * real lock conflict.) It would be possible to run in a transaction
312 : : * block if we had no XID, but this restriction is simpler for users
313 : : * to understand and we don't lose any functionality.
314 : : */
315 : 15 : PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
316 : : }
317 : :
318 : : /*
319 : : * If a single relation is specified, process it and we're done ... unless
320 : : * the relation is a partitioned table, in which case we fall through.
321 : : */
8641 tgl@sss.pgh.pa.us 322 [ + + ]: 232 : if (stmt->relation != NULL)
323 : : {
143 alvherre@kurilemu.de 324 : 216 : rel = process_single_relation(stmt, lockmode, isTopLevel, ¶ms);
170 325 [ + + ]: 172 : if (rel == NULL)
326 : 139 : return; /* all done */
327 : : }
328 : :
329 : : /*
330 : : * Don't allow ANALYZE in the multiple-relation case for now. Maybe we
331 : : * can add support for this later.
332 : : */
333 [ - + ]: 49 : if (params.options & CLUOPT_ANALYZE)
170 alvherre@kurilemu.de 334 [ # # ]:UBC 0 : ereport(ERROR,
335 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
336 : : errmsg("cannot execute %s on multiple tables",
337 : : "REPACK (ANALYZE)"));
338 : :
339 : : /*
340 : : * By here, we know we are in a multi-table situation.
341 : : *
342 : : * Concurrent processing is currently considered rather special (e.g. in
343 : : * terms of resources consumed) so it is not performed in bulk.
344 : : */
143 alvherre@kurilemu.de 345 [ + + ]:CBC 49 : if (params.options & CLUOPT_CONCURRENT)
346 : : {
347 [ + - ]: 1 : if (rel != NULL)
348 : : {
349 [ - + ]: 1 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
350 [ + - ]: 1 : ereport(ERROR,
351 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
352 : : errmsg("%s is not supported for partitioned tables",
353 : : "REPACK (CONCURRENTLY)"),
354 : : errhint("Consider running the command on individual partitions."));
355 : : }
356 : : else
143 alvherre@kurilemu.de 357 [ # # ]:UBC 0 : ereport(ERROR,
358 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
359 : : errmsg("%s requires an explicit table name",
360 : : "REPACK (CONCURRENTLY)"));
361 : : }
362 : :
363 : : /*
364 : : * In order to avoid holding locks for too long, we want to process each
365 : : * table in its own transaction. This forces us to disallow running
366 : : * inside a user transaction block.
367 : : */
170 alvherre@kurilemu.de 368 :CBC 48 : PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command));
369 : :
370 : : /* Also, we need a memory context to hold our list of relations */
371 : 48 : repack_context = AllocSetContextCreate(PortalContext,
372 : : "Repack",
373 : : ALLOCSET_DEFAULT_SIZES);
374 : :
375 : : /*
376 : : * Since we open a new transaction for each relation, we have to check
377 : : * that the relation still is what we think it is.
378 : : *
379 : : * In single-transaction CLUSTER, we don't need the overhead.
380 : : */
381 : 48 : params.options |= CLUOPT_RECHECK;
382 : :
383 : : /*
384 : : * If we don't have a relation yet, determine a relation list. If we do,
385 : : * then it must be a partitioned table, and we want to process its
386 : : * partitions. Note that we don't acquire any locks on these tables, so
387 : : * the returned list must be treated with suspicion.
388 : : */
389 [ + + ]: 48 : if (rel == NULL)
390 : : {
391 [ - + ]: 16 : Assert(stmt->indexname == NULL);
392 : 16 : rtcs = get_tables_to_repack(stmt->command, stmt->usingindex,
393 : : repack_context);
394 : 16 : params.options |= CLUOPT_RECHECK_ISCLUSTERED;
395 : : }
396 : : else
397 : : {
38 398 : 32 : rtcs = get_tables_to_repack_partitioned(stmt, rel, repack_context);
399 : 20 : rel = NULL; /* clobber no longer valid pointer */
400 : : }
401 : :
402 : : /* Commit to get out of starting transaction */
1608 alvherre@alvh.no-ip. 403 : 36 : PopActiveSnapshot();
404 : 36 : CommitTransactionCommand();
405 : :
406 : : /* Cluster the tables, each in a separate transaction */
170 alvherre@kurilemu.de 407 [ - + ]: 36 : Assert(rel == NULL);
408 [ + + + + : 124 : foreach_ptr(RelToCluster, rtc, rtcs)
+ + ]
409 : : {
410 : : /* Start a new transaction for each relation. */
8506 tgl@sss.pgh.pa.us 411 : 52 : StartTransactionCommand();
412 : :
413 : : /*
414 : : * Open the target table. It may have been dropped or replaced with
415 : : * something different, in which case silently skip it.
416 : : */
48 alvherre@kurilemu.de 417 : 52 : rel = try_relation_open(rtc->tableOid, lockmode);
170 418 [ - + ]: 52 : if (rel == NULL)
419 : : {
170 alvherre@kurilemu.de 420 :UBC 0 : CommitTransactionCommand();
421 : 0 : continue;
422 : : }
48 alvherre@kurilemu.de 423 [ - + ]:CBC 52 : if (rel->rd_rel->relkind != RELKIND_RELATION &&
48 alvherre@kurilemu.de 424 [ # # ]:UBC 0 : rel->rd_rel->relkind != RELKIND_MATVIEW)
425 : : {
426 : 0 : relation_close(rel, lockmode);
427 : 0 : CommitTransactionCommand();
428 : 0 : continue;
429 : : }
430 : :
431 : : /* functions in indexes may want a snapshot set */
1608 alvherre@alvh.no-ip. 432 :CBC 52 : PushActiveSnapshot(GetTransactionSnapshot());
433 : :
434 : : /* Process this table */
143 alvherre@kurilemu.de 435 : 52 : cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms, isTopLevel);
436 : : /* cluster_rel closes the relation, but keeps lock */
437 : :
1608 alvherre@alvh.no-ip. 438 : 52 : PopActiveSnapshot();
439 : 52 : CommitTransactionCommand();
440 : : }
441 : :
442 : : /* Start a new transaction for the cleanup work. */
170 alvherre@kurilemu.de 443 : 36 : StartTransactionCommand();
444 : :
445 : : /* Clean up working storage */
446 : 36 : MemoryContextDelete(repack_context);
447 : : }
448 : :
449 : : /*
450 : : * In the non-concurrent case, we obtain AccessExclusiveLock throughout the
451 : : * operation to avoid any lock-upgrade hazards. In the concurrent case, we
452 : : * grab ShareUpdateExclusiveLock (just like VACUUM) for most of the
453 : : * processing and only acquire AccessExclusiveLock at the end, to swap the
454 : : * relation -- supposedly for a short time.
455 : : */
456 : : static LOCKMODE
143 457 : 1478 : RepackLockLevel(bool concurrent)
458 : : {
459 [ + + ]: 1478 : if (concurrent)
460 : 43 : return ShareUpdateExclusiveLock;
461 : : else
462 : 1435 : return AccessExclusiveLock;
463 : : }
464 : :
465 : : /*
466 : : * cluster_rel
467 : : *
468 : : * This clusters the table by creating a new, clustered table and
469 : : * swapping the relfilenumbers of the new table and the old table, so
470 : : * the OID of the original table is preserved. Thus we do not lose
471 : : * GRANT, inheritance nor references to this table.
472 : : *
473 : : * Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
474 : : * the new table, it's better to create the indexes afterwards than to fill
475 : : * them incrementally while we load the table.
476 : : *
477 : : * If indexOid is InvalidOid, the table will be rewritten in physical order
478 : : * instead of index order.
479 : : *
480 : : * Note that, in the concurrent case, the function releases the lock at some
481 : : * point, in order to get AccessExclusiveLock for the final steps (i.e. to
482 : : * swap the relation files). To make things simpler, the caller should expect
483 : : * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The
484 : : * AccessExclusiveLock is kept till the end of the transaction.)
485 : : *
486 : : * 'cmd' indicates which command is being executed, to be used for error
487 : : * messages.
488 : : */
489 : : void
170 490 : 428 : cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
491 : : ClusterParams *params, bool isTopLevel)
492 : : {
594 alvherre@alvh.no-ip. 493 : 428 : Oid tableOid = RelationGetRelid(OldHeap);
494 : : Relation index;
495 : : LOCKMODE lmode;
496 : : Oid save_userid;
497 : : int save_sec_context;
498 : : int save_nestlevel;
2047 michael@paquier.xyz 499 : 428 : bool verbose = ((params->options & CLUOPT_VERBOSE) != 0);
500 : 428 : bool recheck = ((params->options & CLUOPT_RECHECK) != 0);
143 alvherre@kurilemu.de 501 : 428 : bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0);
502 : 428 : Oid ident_idx = InvalidOid;
503 : :
504 : : /* Determine the lock mode to use. */
505 : 428 : lmode = RepackLockLevel(concurrent);
506 : :
507 : : /*
508 : : * Check some preconditions in the concurrent case. This also obtains the
509 : : * replica index OID.
510 : : */
511 [ + + ]: 428 : if (concurrent)
512 : 14 : check_concurrent_repack_requirements(OldHeap, &ident_idx);
513 : :
514 : : /* Check for user-requested abort. */
8686 bruce@momjian.us 515 [ - + ]: 421 : CHECK_FOR_INTERRUPTS();
516 : :
170 alvherre@kurilemu.de 517 : 421 : pgstat_progress_start_command(PROGRESS_COMMAND_REPACK, tableOid);
518 : 421 : pgstat_progress_update_param(PROGRESS_REPACK_COMMAND, cmd);
519 : :
520 : : /*
521 : : * Switch to the table owner's userid, so that any index functions are run
522 : : * as that user. Also lock down security-restricted operations and
523 : : * arrange to make GUC variable changes local to this command.
524 : : */
1571 noah@leadboat.com 525 : 421 : GetUserIdAndSecContext(&save_userid, &save_sec_context);
526 : 421 : SetUserIdAndSecContext(OldHeap->rd_rel->relowner,
527 : : save_sec_context | SECURITY_RESTRICTED_OPERATION);
528 : 421 : save_nestlevel = NewGUCNestLevel();
906 jdavis@postgresql.or 529 : 421 : RestrictSearchPath();
530 : :
531 : : /*
532 : : * Recheck that the relation is still what it was when we started.
533 : : *
534 : : * Note that it's critical to skip this in single-relation CLUSTER;
535 : : * otherwise, we would reject an attempt to cluster using a
536 : : * not-previously-clustered index.
537 : : */
170 alvherre@kurilemu.de 538 [ + + ]: 421 : if (recheck &&
539 [ - + ]: 52 : !cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid,
143 540 : 52 : lmode, params->options))
170 alvherre@kurilemu.de 541 :UBC 0 : goto out;
542 : :
543 : : /*
544 : : * We allow repacking shared catalogs only when not using an index. It
545 : : * would work to use an index in most respects, but the index would only
546 : : * get marked as indisclustered in the current database, leading to
547 : : * unexpected behavior if CLUSTER were later invoked in another database.
548 : : */
6045 tgl@sss.pgh.pa.us 549 [ + + - + ]:CBC 421 : if (OidIsValid(indexOid) && OldHeap->rd_rel->relisshared)
6045 tgl@sss.pgh.pa.us 550 [ # # ]:UBC 0 : ereport(ERROR,
551 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
552 : : /*- translator: first %s is name of a SQL command, eg. REPACK */
553 : : errmsg("cannot execute %s on a shared catalog",
554 : : RepackCommandAsString(cmd)));
555 : :
556 : : /*
557 : : * The CONCURRENTLY case should have been rejected earlier because it does
558 : : * not support system catalogs.
559 : : */
143 alvherre@kurilemu.de 560 [ + + - + ]:CBC 421 : Assert(!(OldHeap->rd_rel->relisshared && concurrent));
561 : :
562 : : /*
563 : : * Don't process temp tables of other backends ... their local buffer
564 : : * manager is not going to cope.
565 : : */
6045 tgl@sss.pgh.pa.us 566 [ + + - + ]: 421 : if (RELATION_IS_OTHER_TEMP(OldHeap))
170 alvherre@kurilemu.de 567 [ # # ]:UBC 0 : ereport(ERROR,
568 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
569 : : /*- translator: first %s is name of a SQL command, eg. REPACK */
570 : : errmsg("cannot execute %s on temporary tables of other sessions",
571 : : RepackCommandAsString(cmd)));
572 : :
573 : : /*
574 : : * Also check for active uses of the relation in the current transaction,
575 : : * including open scans and pending AFTER trigger events.
576 : : */
170 alvherre@kurilemu.de 577 :CBC 421 : CheckTableNotInUse(OldHeap, RepackCommandAsString(cmd));
578 : :
579 : : /* Check heap and index are valid to cluster on */
6045 tgl@sss.pgh.pa.us 580 [ + + ]: 421 : if (OidIsValid(indexOid))
581 : : {
582 : : /* verify the index is good and lock it */
143 alvherre@kurilemu.de 583 : 159 : check_index_is_clusterable(OldHeap, indexOid, lmode);
584 : : /* also open it */
594 alvherre@alvh.no-ip. 585 : 159 : index = index_open(indexOid, NoLock);
586 : : }
587 : : else
588 : 262 : index = NULL;
589 : :
590 : : /*
591 : : * When allow_system_table_mods is turned off, we disallow repacking a
592 : : * catalog on a particular index unless that's already the clustered index
593 : : * for that catalog.
594 : : *
595 : : * XXX We don't check for this in CLUSTER, because it's historically been
596 : : * allowed.
597 : : */
170 alvherre@kurilemu.de 598 [ + + ]: 421 : if (cmd != REPACK_COMMAND_CLUSTER &&
599 [ + - + + ]: 289 : !allowSystemTableMods && OidIsValid(indexOid) &&
600 [ + + + - ]: 27 : IsCatalogRelation(OldHeap) && !index->rd_index->indisclustered)
601 [ + - ]: 4 : ereport(ERROR,
602 : : errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
603 : : errmsg("permission denied: \"%s\" is a system catalog",
604 : : RelationGetRelationName(OldHeap)),
605 : : errdetail("System catalogs can only be clustered by the index they're already clustered on, if any, unless \"%s\" is enabled.",
606 : : "allow_system_table_mods"));
607 : :
608 : : /*
609 : : * Quietly ignore the request if this is a materialized view which has not
610 : : * been populated from its query. No harm is done because there is no data
611 : : * to deal with, and we don't want to throw an error if this is part of a
612 : : * multi-relation request -- for example, CLUSTER was run on the entire
613 : : * database.
614 : : */
4925 kgrittn@postgresql.o 615 [ + + ]: 417 : if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
4861 tgl@sss.pgh.pa.us 616 [ + - ]: 8 : !RelationIsPopulated(OldHeap))
617 : : {
143 alvherre@kurilemu.de 618 [ + - ]: 8 : if (index)
619 : 8 : index_close(index, lmode);
620 : 8 : relation_close(OldHeap, lmode);
1571 noah@leadboat.com 621 : 8 : goto out;
622 : : }
623 : :
1608 alvherre@alvh.no-ip. 624 [ + + + - : 409 : Assert(OldHeap->rd_rel->relkind == RELKIND_RELATION ||
- + ]
625 : : OldHeap->rd_rel->relkind == RELKIND_MATVIEW ||
626 : : OldHeap->rd_rel->relkind == RELKIND_TOASTVALUE);
627 : :
628 : : /*
629 : : * All predicate locks on the tuples or pages are about to be made
630 : : * invalid, because we move tuples around. Promote them to relation
631 : : * locks. Predicate locks on indexes will be promoted when they are
632 : : * reindexed.
633 : : *
634 : : * During concurrent processing, the heap as well as its indexes stay in
635 : : * operation, so we postpone this step until they are locked using
636 : : * AccessExclusiveLock near the end of the processing.
637 : : */
143 alvherre@kurilemu.de 638 [ + + ]: 409 : if (!concurrent)
639 : 402 : TransferPredicateLocksToHeapRelation(OldHeap);
640 : :
641 : : /*
642 : : * rebuild_relation does all the dirty work, and closes OldHeap and index,
643 : : * if valid.
644 : : *
645 : : * In concurrent mode, make sure the worker terminates; normally it does
646 : : * so by itself, but a PG_ENSURE_ERROR_CLEANUP callback ensures that this
647 : : * happens even in case this backend dies early on a FATAL exit. Normal
648 : : * mode doesn't need that overhead.
649 : : */
100 650 [ + + ]: 409 : if (concurrent)
651 : : {
652 [ + - ]: 7 : PG_ENSURE_ERROR_CLEANUP(stop_repack_decoding_worker_cb, 0);
653 : : {
654 : 7 : rebuild_relation(OldHeap, index, verbose, ident_idx);
655 : : }
656 [ - + ]: 7 : PG_END_ENSURE_ERROR_CLEANUP(stop_repack_decoding_worker_cb, 0);
657 : 7 : stop_repack_decoding_worker();
658 : : }
659 : : else
660 : 402 : rebuild_relation(OldHeap, index, verbose, ident_idx);
661 : :
1571 noah@leadboat.com 662 : 413 : out:
663 : : /* Roll back any GUC changes executed by index functions */
664 : 413 : AtEOXact_GUC(false, save_nestlevel);
665 : :
666 : : /* Restore userid and security context */
667 : 413 : SetUserIdAndSecContext(save_userid, save_sec_context);
668 : :
2712 rhaas@postgresql.org 669 : 413 : pgstat_progress_end_command();
8148 tgl@sss.pgh.pa.us 670 : 413 : }
671 : :
672 : : /*
673 : : * Check if the table (and its index) still meets the requirements of
674 : : * cluster_rel().
675 : : */
676 : : static bool
170 alvherre@kurilemu.de 677 : 52 : cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid,
678 : : Oid userid, LOCKMODE lmode, int options)
679 : : {
680 : 52 : Oid tableOid = RelationGetRelid(OldHeap);
681 : :
48 682 [ - + ]: 52 : Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
683 : :
684 : : /* Check that the user still has privileges for the relation */
8 685 [ - + ]: 52 : if (!repack_is_permitted_for_relation(cmd, tableOid, userid, true))
686 : : {
143 alvherre@kurilemu.de 687 :UBC 0 : relation_close(OldHeap, lmode);
170 688 : 0 : return false;
689 : : }
690 : :
691 : : /*
692 : : * Silently skip a temp table for a remote session. Only doing this check
693 : : * in the "recheck" case is appropriate (which currently means somebody is
694 : : * executing a database-wide CLUSTER or on a partitioned table), because
695 : : * there is another check in cluster() which will stop any attempt to
696 : : * cluster remote temp tables by name. There is another check in
697 : : * cluster_rel which is redundant, but we leave it for extra safety.
698 : : */
170 alvherre@kurilemu.de 699 [ - + - - ]:CBC 52 : if (RELATION_IS_OTHER_TEMP(OldHeap))
700 : : {
143 alvherre@kurilemu.de 701 :UBC 0 : relation_close(OldHeap, lmode);
170 702 : 0 : return false;
703 : : }
704 : :
170 alvherre@kurilemu.de 705 [ + + ]:CBC 52 : if (OidIsValid(indexOid))
706 : : {
707 : : /*
708 : : * Check that the index still exists
709 : : */
710 [ - + ]: 32 : if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid)))
711 : : {
143 alvherre@kurilemu.de 712 :UBC 0 : relation_close(OldHeap, lmode);
170 713 : 0 : return false;
714 : : }
715 : :
716 : : /*
717 : : * Check that the index is still the one with indisclustered set, if
718 : : * needed.
719 : : */
170 alvherre@kurilemu.de 720 [ + + ]:CBC 32 : if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 &&
721 [ - + ]: 4 : !get_index_isclustered(indexOid))
722 : : {
143 alvherre@kurilemu.de 723 :UBC 0 : relation_close(OldHeap, lmode);
170 724 : 0 : return false;
725 : : }
726 : : }
727 : :
170 alvherre@kurilemu.de 728 :CBC 52 : return true;
729 : : }
730 : :
731 : : /*
732 : : * Verify that the specified heap and index are valid to cluster on
733 : : *
734 : : * Side effect: obtains lock on the index. The caller may
735 : : * in some cases already have a lock of the same strength on the table, but
736 : : * not in all cases so we can't rely on the table-level lock for
737 : : * protection here.
738 : : */
739 : : void
1597 michael@paquier.xyz 740 : 357 : check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode)
741 : : {
742 : : Relation OldIndex;
743 : :
5873 simon@2ndQuadrant.co 744 : 357 : OldIndex = index_open(indexOid, lockmode);
745 : :
746 : : /*
747 : : * Check that index is in fact an index on the given relation
748 : : */
8782 tgl@sss.pgh.pa.us 749 [ + - ]: 357 : if (OldIndex->rd_index == NULL ||
8148 750 [ + + ]: 357 : OldIndex->rd_index->indrelid != RelationGetRelid(OldHeap))
8439 751 [ + - ]: 4 : ereport(ERROR,
752 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
753 : : errmsg("\"%s\" is not an index for table \"%s\"",
754 : : RelationGetRelationName(OldIndex),
755 : : RelationGetRelationName(OldHeap))));
756 : :
757 : : /* Index AM must allow clustering */
2775 andres@anarazel.de 758 [ + + ]: 353 : if (!OldIndex->rd_indam->amclusterable)
6045 tgl@sss.pgh.pa.us 759 [ + - ]: 4 : ereport(ERROR,
760 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
761 : : errmsg("cannot cluster on index \"%s\" because access method does not support clustering",
762 : : RelationGetRelationName(OldIndex))));
763 : :
764 : : /*
765 : : * Disallow clustering on incomplete indexes (those that might not index
766 : : * every row of the relation). We could relax this by making a separate
767 : : * seqscan pass over the table to copy the missing rows, but that seems
768 : : * expensive and tedious.
769 : : */
3074 andrew@dunslane.net 770 [ + + ]: 349 : if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred, NULL))
8439 tgl@sss.pgh.pa.us 771 [ + - ]: 4 : ereport(ERROR,
772 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
773 : : errmsg("cannot cluster on partial index \"%s\"",
774 : : RelationGetRelationName(OldIndex))));
775 : :
776 : : /*
777 : : * Disallow if index is left over from a failed CREATE INDEX CONCURRENTLY;
778 : : * it might well not contain entries for every heap row, or might not even
779 : : * be internally consistent. (But note that we don't check indcheckxmin;
780 : : * the worst consequence of following broken HOT chains would be that we
781 : : * might put recently-dead tuples out-of-order in the new table, and there
782 : : * is little harm in that.)
783 : : */
2800 peter_e@gmx.net 784 [ + + ]: 345 : if (!OldIndex->rd_index->indisvalid)
6907 tgl@sss.pgh.pa.us 785 [ + - ]: 4 : ereport(ERROR,
786 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
787 : : errmsg("cannot cluster on invalid index \"%s\"",
788 : : RelationGetRelationName(OldIndex))));
789 : :
790 : : /* Drop relcache refcnt on OldIndex, but keep lock */
7332 791 : 341 : index_close(OldIndex, NoLock);
8678 bruce@momjian.us 792 : 341 : }
793 : :
794 : : /*
795 : : * mark_index_clustered: mark the specified index as the one clustered on
796 : : *
797 : : * With indexOid == InvalidOid, will mark all indexes of rel not-clustered.
798 : : */
799 : : void
4911 rhaas@postgresql.org 800 : 194 : mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
801 : : {
802 : : HeapTuple indexTuple;
803 : : Form_pg_index indexForm;
804 : : Relation pg_index;
805 : : ListCell *index;
806 : :
164 michael@paquier.xyz 807 [ - + ]: 194 : Assert(rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE);
808 : :
809 : : /*
810 : : * If the index is already marked clustered, no need to do anything.
811 : : */
8146 tgl@sss.pgh.pa.us 812 [ + + ]: 194 : if (OidIsValid(indexOid))
813 : : {
2334 michael@paquier.xyz 814 [ + + ]: 186 : if (get_index_isclustered(indexOid))
8146 tgl@sss.pgh.pa.us 815 : 39 : return;
816 : : }
817 : :
818 : : /*
819 : : * Check each index of the relation and set/clear the bit as needed.
820 : : */
2775 andres@anarazel.de 821 : 155 : pg_index = table_open(IndexRelationId, RowExclusiveLock);
822 : :
8146 tgl@sss.pgh.pa.us 823 [ + - + + : 464 : foreach(index, RelationGetIndexList(rel))
+ + ]
824 : : {
8033 bruce@momjian.us 825 : 309 : Oid thisIndexOid = lfirst_oid(index);
826 : :
6038 rhaas@postgresql.org 827 : 309 : indexTuple = SearchSysCacheCopy1(INDEXRELID,
828 : : ObjectIdGetDatum(thisIndexOid));
8146 tgl@sss.pgh.pa.us 829 [ - + ]: 309 : if (!HeapTupleIsValid(indexTuple))
8146 tgl@sss.pgh.pa.us 830 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
8146 tgl@sss.pgh.pa.us 831 :CBC 309 : indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
832 : :
833 : : /*
834 : : * Unset the bit if set. We know it's wrong because we checked this
835 : : * earlier.
836 : : */
837 [ + + ]: 309 : if (indexForm->indisclustered)
838 : : {
839 : 20 : indexForm->indisclustered = false;
3495 alvherre@alvh.no-ip. 840 : 20 : CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
841 : : }
8146 tgl@sss.pgh.pa.us 842 [ + + ]: 289 : else if (thisIndexOid == indexOid)
843 : : {
844 : : /* this was checked earlier, but let's be real sure */
2800 peter_e@gmx.net 845 [ - + ]: 147 : if (!indexForm->indisvalid)
5020 tgl@sss.pgh.pa.us 846 [ # # ]:UBC 0 : elog(ERROR, "cannot cluster on invalid index %u", indexOid);
8146 tgl@sss.pgh.pa.us 847 :CBC 147 : indexForm->indisclustered = true;
3495 alvherre@alvh.no-ip. 848 : 147 : CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
849 : : }
850 : :
4911 rhaas@postgresql.org 851 [ - + ]: 309 : InvokeObjectPostAlterHookArg(IndexRelationId, thisIndexOid, 0,
852 : : InvalidOid, is_internal);
853 : :
8146 tgl@sss.pgh.pa.us 854 : 309 : heap_freetuple(indexTuple);
855 : : }
856 : :
2775 andres@anarazel.de 857 : 155 : table_close(pg_index, RowExclusiveLock);
858 : : }
859 : :
860 : : /*
861 : : * Check if the CONCURRENTLY option is legal for the relation.
862 : : *
863 : : * *Ident_idx_p receives OID of the identity index.
864 : : */
865 : : static void
143 alvherre@kurilemu.de 866 : 14 : check_concurrent_repack_requirements(Relation rel, Oid *ident_idx_p)
867 : : {
868 : : char relpersistence,
869 : : replident;
870 : : Oid ident_idx;
871 : :
91 872 [ - + ]: 14 : if (wal_level < WAL_LEVEL_REPLICA)
91 alvherre@kurilemu.de 873 [ # # ]:UBC 0 : ereport(ERROR,
874 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
875 : : errmsg("cannot execute %s in this configuration",
876 : : "REPACK (CONCURRENTLY)"),
877 : : errdetail("%s requires \"wal_level\" to be set to \"replica\" or higher.",
878 : : "REPACK (CONCURRENTLY)"));
879 : :
880 : : /* Data changes in system relations are not logically decoded. */
143 alvherre@kurilemu.de 881 [ + + ]:CBC 14 : if (IsCatalogRelation(rel))
882 [ + - ]: 1 : ereport(ERROR,
883 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
884 : : errmsg("cannot execute %s on relation \"%s\"",
885 : : "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
886 : : errhint("%s is not supported for catalog relations.",
887 : : "REPACK (CONCURRENTLY)"));
888 : :
889 : : /*
890 : : * reorderbuffer.c does not seem to handle processing of TOAST relation
891 : : * alone.
892 : : */
893 [ + + ]: 13 : if (IsToastRelation(rel))
894 [ + - ]: 1 : ereport(ERROR,
895 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
896 : : errmsg("cannot execute %s on relation \"%s\"",
897 : : "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
898 : : errhint("%s is not supported for TOAST relations.",
899 : : "REPACK (CONCURRENTLY)"));
900 : :
901 : 12 : relpersistence = rel->rd_rel->relpersistence;
902 [ + + ]: 12 : if (relpersistence != RELPERSISTENCE_PERMANENT)
903 [ + - ]: 2 : ereport(ERROR,
904 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
905 : : errmsg("cannot execute %s on relation \"%s\"",
906 : : "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
907 : : errhint("%s is only allowed for permanent relations.",
908 : : "REPACK (CONCURRENTLY)"));
909 : :
910 : : /*
911 : : * With NOTHING, WAL does not contain the old tuple; FULL is not yet
912 : : * supported.
913 : : */
914 : 10 : replident = rel->rd_rel->relreplident;
91 915 [ + + - + ]: 10 : if (replident == REPLICA_IDENTITY_NOTHING ||
916 : : replident == REPLICA_IDENTITY_FULL)
143 917 [ + - + - ]: 1 : ereport(ERROR,
918 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
919 : : errmsg("cannot execute %s on relation \"%s\"",
920 : : "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
921 : : errdetail("%s does not support tables with %s.",
922 : : "REPACK (CONCURRENTLY)",
923 : : replident == REPLICA_IDENTITY_NOTHING ?
924 : : "REPLICA IDENTITY NOTHING" : "REPLICA IDENTITY FULL"));
925 : :
926 : : /*
927 : : * Obtain the replica identity index -- either one that has been set
928 : : * explicitly, or a non-deferrable primary key. If none of these cases
929 : : * apply, the table cannot be repacked concurrently. It might be possible
930 : : * to have repack work with a FULL replica identity; however that requires
931 : : * more work and is not implemented yet.
932 : : */
122 933 : 9 : ident_idx = GetRelationIdentityOrPK(rel);
143 934 [ + + ]: 9 : if (!OidIsValid(ident_idx))
935 : : {
936 : : /* This special case warrants its own error message */
91 937 [ + + + - ]: 2 : if (OidIsValid(rel->rd_pkindex) && rel->rd_ispkdeferrable)
938 [ + - ]: 1 : ereport(ERROR,
939 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
940 : : errmsg("cannot execute %s on relation \"%s\"",
941 : : "REPACK (CONCURRENTLY)",
942 : : RelationGetRelationName(rel)),
943 : : errdetail("%s does not support deferrable primary keys.",
944 : : "REPACK (CONCURRENTLY)"),
945 : : errhint("Use ALTER TABLE ... REPLICA IDENTITY USING INDEX to designate another index as replica identity."));
946 : :
143 947 [ + - ]: 1 : ereport(ERROR,
948 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
949 : : errmsg("cannot execute %s on relation \"%s\"",
950 : : "REPACK (CONCURRENTLY)", RelationGetRelationName(rel)),
951 : : errhint("Relation \"%s\" has no identity index.",
952 : : RelationGetRelationName(rel)));
953 : : }
954 : :
955 : 7 : *ident_idx_p = ident_idx;
956 : 7 : }
957 : :
958 : :
959 : : /*
960 : : * rebuild_relation: rebuild an existing relation in index or physical order
961 : : *
962 : : * OldHeap: table to rebuild. See cluster_rel() for comments on the required
963 : : * lock strength.
964 : : *
965 : : * index: index to cluster by, or NULL to rewrite in physical order.
966 : : *
967 : : * ident_idx: identity index, to handle replaying of concurrent data changes
968 : : * to the new heap. InvalidOid if there's no CONCURRENTLY option.
969 : : *
970 : : * On entry, heap and index (if one is given) must be open, and the
971 : : * appropriate lock held on them -- AccessExclusiveLock for exclusive
972 : : * processing and ShareUpdateExclusiveLock for concurrent processing.
973 : : *
974 : : * On exit, they are closed, but still locked with AccessExclusiveLock.
975 : : * (The function handles the lock upgrade if 'concurrent' is true.)
976 : : */
977 : : static void
978 : 409 : rebuild_relation(Relation OldHeap, Relation index, bool verbose,
979 : : Oid ident_idx)
980 : : {
8641 tgl@sss.pgh.pa.us 981 : 409 : Oid tableOid = RelationGetRelid(OldHeap);
1856 michael@paquier.xyz 982 : 409 : Oid accessMethod = OldHeap->rd_rel->relam;
8082 tgl@sss.pgh.pa.us 983 : 409 : Oid tableSpace = OldHeap->rd_rel->reltablespace;
984 : : Oid OIDNewHeap;
985 : : Relation NewHeap;
986 : : char relpersistence;
987 : : bool swap_toast_by_content;
988 : : TransactionId frozenXid;
989 : : MultiXactId cutoffMulti;
143 alvherre@kurilemu.de 990 : 409 : bool concurrent = OidIsValid(ident_idx);
991 : 409 : Snapshot snapshot = NULL;
992 : : #if USE_ASSERT_CHECKING
993 : : LOCKMODE lmode;
994 : :
995 : 409 : lmode = RepackLockLevel(concurrent);
996 : :
997 [ - + ]: 409 : Assert(CheckRelationLockedByMe(OldHeap, lmode, false));
998 [ + + - + ]: 409 : Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false));
999 : : #endif
1000 : :
1001 [ + + ]: 409 : if (concurrent)
1002 : : {
1003 : : /*
1004 : : * The worker needs to be member of the locking group we're the leader
1005 : : * of. We ought to become the leader before the worker starts. The
1006 : : * worker will join the group as soon as it starts.
1007 : : *
1008 : : * This is to make sure that the deadlock described below is
1009 : : * detectable by deadlock.c: if the worker waits for a transaction to
1010 : : * complete and we are waiting for the worker output, then effectively
1011 : : * we (i.e. this backend) are waiting for that transaction.
1012 : : */
1013 : 7 : BecomeLockGroupLeader();
1014 : :
1015 : : /*
1016 : : * Start the worker that decodes data changes applied while we're
1017 : : * copying the table contents.
1018 : : *
1019 : : * Note that the worker has to wait for all transactions with XID
1020 : : * already assigned to finish. If some of those transactions is
1021 : : * waiting for a lock conflicting with ShareUpdateExclusiveLock on our
1022 : : * table (e.g. it runs CREATE INDEX), we can end up in a deadlock.
1023 : : * Not sure this risk is worth unlocking/locking the table (and its
1024 : : * clustering index) and checking again if it's still eligible for
1025 : : * REPACK CONCURRENTLY.
1026 : : */
1027 : 7 : start_repack_decoding_worker(tableOid);
1028 : :
1029 : : /*
1030 : : * Wait until the worker has the initial snapshot and retrieve it.
1031 : : */
1032 : 7 : snapshot = get_initial_snapshot(decoding_worker);
1033 : :
1034 : 7 : PushActiveSnapshot(snapshot);
1035 : : }
1036 : :
1037 : : /* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */
170 1038 [ + + ]: 409 : if (index != NULL)
594 alvherre@alvh.no-ip. 1039 : 147 : mark_index_clustered(OldHeap, RelationGetRelid(index), true);
1040 : :
1041 : : /* Remember info about rel before closing OldHeap */
3463 tgl@sss.pgh.pa.us 1042 : 409 : relpersistence = OldHeap->rd_rel->relpersistence;
1043 : :
1044 : : /*
1045 : : * Create the transient table that will receive the re-ordered data.
1046 : : *
1047 : : * OldHeap is already locked, so no need to lock it again. make_new_heap
1048 : : * obtains AccessExclusiveLock on the new heap and its toast table.
1049 : : */
4388 alvherre@alvh.no-ip. 1050 : 409 : OIDNewHeap = make_new_heap(tableOid, tableSpace,
1051 : : accessMethod,
1052 : : relpersistence,
1053 : : NoLock);
594 1054 [ - + ]: 409 : Assert(CheckRelationOidLockedByMe(OIDNewHeap, AccessExclusiveLock, false));
1055 : 409 : NewHeap = table_open(OIDNewHeap, NoLock);
1056 : :
1057 : : /*
1058 : : * In concurrent mode, create a copy of the attribute defaults on the temp
1059 : : * table, which the executor needs when replaying concurrent data changes.
1060 : : */
55 alvherre@kurilemu.de 1061 [ + + ]: 409 : if (concurrent)
1062 : 7 : copy_attribute_defaults(tableOid, OIDNewHeap);
1063 : :
1064 : : /* Copy the heap data into the new table in the desired order */
143 1065 : 409 : copy_table_data(NewHeap, OldHeap, index, snapshot, verbose,
1066 : : &swap_toast_by_content, &frozenXid, &cutoffMulti);
1067 : :
1068 : : /* The historic snapshot won't be needed anymore. */
1069 [ + + ]: 409 : if (snapshot)
1070 : : {
1071 : 7 : PopActiveSnapshot();
1072 : 7 : UpdateActiveSnapshotCommandId();
1073 : : }
1074 : :
1075 [ + + ]: 409 : if (concurrent)
1076 : : {
1077 [ - + ]: 7 : Assert(!swap_toast_by_content);
1078 : :
1079 : : /*
1080 : : * Close the index, but keep the lock. Both heaps will be closed by
1081 : : * the following call.
1082 : : */
1083 [ + + ]: 7 : if (index)
1084 : 3 : index_close(index, NoLock);
1085 : :
1086 : 7 : rebuild_relation_finish_concurrent(NewHeap, OldHeap, ident_idx,
1087 : : frozenXid, cutoffMulti);
1088 : :
1089 : 7 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
1090 : : PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
1091 : : }
1092 : : else
1093 : : {
1094 : 402 : bool is_system_catalog = IsSystemRelation(OldHeap);
1095 : :
1096 : : /* Close relcache entries, but keep lock until transaction commit */
1097 : 402 : table_close(OldHeap, NoLock);
1098 [ + + ]: 402 : if (index)
1099 : 144 : index_close(index, NoLock);
1100 : :
1101 : : /*
1102 : : * Close the new relation so it can be dropped as soon as the storage
1103 : : * is swapped. The relation is not visible to others, so no need to
1104 : : * unlock it explicitly.
1105 : : */
1106 : 402 : table_close(NewHeap, NoLock);
1107 : :
1108 : : /*
1109 : : * Swap the physical files of the target and transient tables, then
1110 : : * rebuild the target's indexes and throw away the transient table.
1111 : : */
1112 : 402 : finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog,
1113 : : swap_toast_by_content, false, true,
1114 : : true, /* reindex */
1115 : : frozenXid, cutoffMulti,
1116 : : relpersistence);
1117 : : }
11006 scrappy@hub.org 1118 : 405 : }
1119 : :
1120 : :
1121 : : /*
1122 : : * Create the transient table that will be filled with new data during
1123 : : * CLUSTER, ALTER TABLE, and similar operations. The transient table
1124 : : * duplicates the logical structure of the OldHeap; but will have the
1125 : : * specified physical storage properties NewTableSpace, NewAccessMethod, and
1126 : : * relpersistence.
1127 : : *
1128 : : * After this, the caller should load the new heap with transferred/modified
1129 : : * data, then call finish_heap_swap to complete the operation.
1130 : : */
1131 : : Oid
1856 michael@paquier.xyz 1132 : 1576 : make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod,
1133 : : char relpersistence, LOCKMODE lockmode)
1134 : : {
1135 : : TupleDesc OldHeapDesc;
1136 : : char NewHeapName[NAMEDATALEN];
1137 : : Oid OIDNewHeap;
1138 : : Oid toastid;
1139 : : Relation OldHeap;
1140 : : HeapTuple tuple;
1141 : : Datum reloptions;
1142 : : bool isNull;
1143 : : Oid namespaceid;
1144 : :
2775 andres@anarazel.de 1145 : 1576 : OldHeap = table_open(OIDOldHeap, lockmode);
10222 bruce@momjian.us 1146 : 1576 : OldHeapDesc = RelationGetDescr(OldHeap);
1147 : :
1148 : : /*
1149 : : * Note that the NewHeap will not receive any of the defaults or
1150 : : * constraints associated with the OldHeap; we don't need 'em, and there's
1151 : : * no reason to spend cycles inserting them into the catalogs only to
1152 : : * delete them.
1153 : : */
1154 : :
1155 : : /*
1156 : : * But we do want to use reloptions of the old heap for new heap.
1157 : : */
6038 rhaas@postgresql.org 1158 : 1576 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(OIDOldHeap));
7360 tgl@sss.pgh.pa.us 1159 [ - + ]: 1576 : if (!HeapTupleIsValid(tuple))
7360 tgl@sss.pgh.pa.us 1160 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", OIDOldHeap);
7360 tgl@sss.pgh.pa.us 1161 :CBC 1576 : reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
1162 : : &isNull);
1163 [ + + ]: 1576 : if (isNull)
1164 : 1483 : reloptions = (Datum) 0;
1165 : :
4388 alvherre@alvh.no-ip. 1166 [ + + ]: 1576 : if (relpersistence == RELPERSISTENCE_TEMP)
4790 kgrittn@postgresql.o 1167 : 98 : namespaceid = LookupCreationNamespace("pg_temp");
1168 : : else
1169 : 1478 : namespaceid = RelationGetNamespace(OldHeap);
1170 : :
1171 : : /*
1172 : : * Create the new heap, using a temporary name in the same namespace as
1173 : : * the existing table. NOTE: there is some risk of collision with user
1174 : : * relnames. Working around this seems more trouble than it's worth; in
1175 : : * particular, we can't create the new heap in a different namespace from
1176 : : * the old, or we will have problems with the TEMP status of temp tables.
1177 : : *
1178 : : * Note: the new heap is not a shared relation, even if we are rebuilding
1179 : : * a shared rel. However, we do make the new heap mapped if the source is
1180 : : * mapped. This simplifies swap_relation_files, and is absolutely
1181 : : * necessary for rebuilding pg_class, for reasons explained there.
1182 : : */
6048 tgl@sss.pgh.pa.us 1183 : 1576 : snprintf(NewHeapName, sizeof(NewHeapName), "pg_temp_%u", OIDOldHeap);
1184 : :
1185 : 1576 : OIDNewHeap = heap_create_with_catalog(NewHeapName,
1186 : : namespaceid,
1187 : : NewTableSpace,
1188 : : InvalidOid,
1189 : : InvalidOid,
1190 : : InvalidOid,
7671 1191 : 1576 : OldHeap->rd_rel->relowner,
1192 : : NewAccessMethod,
1193 : : OldHeapDesc,
1194 : : NIL,
1195 : : RELKIND_RELATION,
1196 : : relpersistence,
1197 : : false,
6045 1198 [ + + + - : 1576 : RelationIsMapped(OldHeap),
+ - + + +
- + + ]
1199 : : ONCOMMIT_NOOP,
1200 : : reloptions,
1201 : : false,
1202 : : true,
1203 : : true,
1204 : : OIDOldHeap,
4195 alvherre@alvh.no-ip. 1205 : 1576 : NULL);
5877 rhaas@postgresql.org 1206 [ - + ]: 1576 : Assert(OIDNewHeap != InvalidOid);
1207 : :
7361 bruce@momjian.us 1208 : 1576 : ReleaseSysCache(tuple);
1209 : :
1210 : : /*
1211 : : * Advance command counter so that the newly-created relation's catalog
1212 : : * tuples will be visible to table_open.
1213 : : */
9369 tgl@sss.pgh.pa.us 1214 : 1576 : CommandCounterIncrement();
1215 : :
1216 : : /*
1217 : : * If necessary, create a TOAST table for the new relation.
1218 : : *
1219 : : * If the relation doesn't have a TOAST table already, we can't need one
1220 : : * for the new relation. The other way around is possible though: if some
1221 : : * wide columns have been dropped, NewHeapCreateToastTable can decide that
1222 : : * no TOAST table is needed for the new table.
1223 : : *
1224 : : * Note that NewHeapCreateToastTable ends with CommandCounterIncrement, so
1225 : : * that the TOAST table will be visible for insertion.
1226 : : */
6415 alvherre@alvh.no-ip. 1227 : 1576 : toastid = OldHeap->rd_rel->reltoastrelid;
1228 [ + + ]: 1576 : if (OidIsValid(toastid))
1229 : : {
1230 : : /* keep the existing toast table's reloptions, if any */
6038 rhaas@postgresql.org 1231 : 571 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(toastid));
6415 alvherre@alvh.no-ip. 1232 [ - + ]: 571 : if (!HeapTupleIsValid(tuple))
6415 alvherre@alvh.no-ip. 1233 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", toastid);
6415 alvherre@alvh.no-ip. 1234 :CBC 571 : reloptions = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_reloptions,
1235 : : &isNull);
1236 [ + - ]: 571 : if (isNull)
1237 : 571 : reloptions = (Datum) 0;
1238 : :
1828 akapila@postgresql.o 1239 : 571 : NewHeapCreateToastTable(OIDNewHeap, reloptions, lockmode, toastid);
1240 : :
6415 alvherre@alvh.no-ip. 1241 : 571 : ReleaseSysCache(tuple);
1242 : : }
1243 : :
2775 andres@anarazel.de 1244 : 1576 : table_close(OldHeap, NoLock);
1245 : :
9423 tgl@sss.pgh.pa.us 1246 : 1576 : return OIDNewHeap;
1247 : : }
1248 : :
1249 : : /*
1250 : : * Do the physical copying of table data.
1251 : : *
1252 : : * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass
1253 : : * iff concurrent processing is required.
1254 : : *
1255 : : * There are three output parameters:
1256 : : * *pSwapToastByContent is set true if toast tables must be swapped by content.
1257 : : * *pFreezeXid receives the TransactionId used as freeze cutoff point.
1258 : : * *pCutoffMulti receives the MultiXactId used as a cutoff point.
1259 : : */
1260 : : static void
143 alvherre@kurilemu.de 1261 : 409 : copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex,
1262 : : Snapshot snapshot, bool verbose, bool *pSwapToastByContent,
1263 : : TransactionId *pFreezeXid, MultiXactId *pCutoffMulti)
1264 : : {
1265 : : Relation relRelation;
1266 : : HeapTuple reltup;
1267 : : Form_pg_class relform;
1268 : : TupleDesc oldTupDesc PG_USED_FOR_ASSERTS_ONLY;
1269 : : TupleDesc newTupDesc PG_USED_FOR_ASSERTS_ONLY;
1270 : : VacuumParams params;
1271 : : struct VacuumCutoffs cutoffs;
1272 : : bool use_sort;
5803 tgl@sss.pgh.pa.us 1273 : 409 : double num_tuples = 0,
1274 : 409 : tups_vacuumed = 0,
1275 : 409 : tups_recently_dead = 0;
1276 : : BlockNumber num_pages;
1277 [ + + ]: 409 : int elevel = verbose ? INFO : DEBUG2;
1278 : : PGRUsage ru0;
1279 : : char *nspname;
143 alvherre@kurilemu.de 1280 : 409 : bool concurrent = snapshot != NULL;
1281 : : LOCKMODE lmode;
1282 : :
1283 : 409 : lmode = RepackLockLevel(concurrent);
1284 : :
5803 tgl@sss.pgh.pa.us 1285 : 409 : pg_rusage_init(&ru0);
1286 : :
1287 : : /* Store a copy of the namespace name for logging purposes */
1837 dgustafsson@postgres 1288 : 409 : nspname = get_namespace_name(RelationGetNamespace(OldHeap));
1289 : :
1290 : : /*
1291 : : * Their tuple descriptors should be exactly alike, but here we only need
1292 : : * assume that they have the same number of columns.
1293 : : */
7872 tgl@sss.pgh.pa.us 1294 : 409 : oldTupDesc = RelationGetDescr(OldHeap);
1295 : 409 : newTupDesc = RelationGetDescr(NewHeap);
1296 [ - + ]: 409 : Assert(newTupDesc->natts == oldTupDesc->natts);
1297 : :
1298 : : /*
1299 : : * If the OldHeap has a toast table, get lock on the toast table to keep
1300 : : * it from being vacuumed. This is needed because autovacuum processes
1301 : : * toast tables independently of their main tables, with no lock on the
1302 : : * latter. If an autovacuum were to start on the toast table after we
1303 : : * compute our OldestXmin below, it would use a later OldestXmin, and then
1304 : : * possibly remove as DEAD toast tuples belonging to main tuples we think
1305 : : * are only RECENTLY_DEAD. Then we'd fail while trying to copy those
1306 : : * tuples.
1307 : : *
1308 : : * We don't need to open the toast relation here, just lock it. The lock
1309 : : * will be held till end of transaction.
1310 : : */
5597 1311 [ + + ]: 409 : if (OldHeap->rd_rel->reltoastrelid)
143 alvherre@kurilemu.de 1312 : 135 : LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode);
1313 : :
1314 : : /*
1315 : : * If both tables have TOAST tables, perform toast swap by content. It is
1316 : : * possible that the old table has a toast table but the new one doesn't,
1317 : : * if toastable columns have been dropped. In that case we have to do
1318 : : * swap by links. This is okay because swap by content is only essential
1319 : : * for system catalogs, and we don't support schema changes for them.
1320 : : */
1321 [ + + + - ]: 409 : if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid &&
1322 [ + + ]: 135 : !concurrent)
1323 : : {
6048 tgl@sss.pgh.pa.us 1324 : 132 : *pSwapToastByContent = true;
1325 : :
1326 : : /*
1327 : : * When doing swap by content, any toast pointers written into NewHeap
1328 : : * must use the old toast table's OID, because that's where the toast
1329 : : * data will eventually be found. Set this up by setting rd_toastoid.
1330 : : * This also tells toast_save_datum() to preserve the toast value
1331 : : * OIDs, which we want so as not to invalidate toast pointers in
1332 : : * system catalog caches, and to avoid making multiple copies of a
1333 : : * single toast value.
1334 : : *
1335 : : * Note that we must hold NewHeap open until we are done writing data,
1336 : : * since the relcache will not guarantee to remember this setting once
1337 : : * the relation is closed. Also, this technique depends on the fact
1338 : : * that no one will try to read from the NewHeap until after we've
1339 : : * finished writing it and swapping the rels --- otherwise they could
1340 : : * follow the toast pointers to the wrong place. (It would actually
1341 : : * work for values copied over from the old toast table, but not for
1342 : : * any values that we toast which were previously not toasted.)
1343 : : *
1344 : : * This would not work with CONCURRENTLY because we may need to delete
1345 : : * TOASTed tuples from the new heap. With this hack, we'd delete them
1346 : : * from the old heap.
1347 : : */
1348 : 132 : NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid;
1349 : : }
1350 : : else
1351 : 277 : *pSwapToastByContent = false;
1352 : :
1353 : : /*
1354 : : * Compute xids used to freeze and weed out dead tuples and multixacts.
1355 : : * Since we're going to rewrite the whole table anyway, there's no reason
1356 : : * not to be aggressive about this.
1357 : : */
1373 pg@bowt.ie 1358 : 409 : memset(¶ms, 0, sizeof(VacuumParams));
149 nathan@postgresql.or 1359 : 409 : vacuum_get_cutoffs(OldHeap, ¶ms, &cutoffs);
1360 : :
1361 : : /*
1362 : : * FreezeXid will become the table's new relfrozenxid, and that mustn't go
1363 : : * backwards, so take the max.
1364 : : */
1365 : : {
850 noah@leadboat.com 1366 : 409 : TransactionId relfrozenxid = OldHeap->rd_rel->relfrozenxid;
1367 : :
1368 [ + - + + ]: 818 : if (TransactionIdIsValid(relfrozenxid) &&
1369 : 409 : TransactionIdPrecedes(cutoffs.FreezeLimit, relfrozenxid))
1370 : 27 : cutoffs.FreezeLimit = relfrozenxid;
1371 : : }
1372 : :
1373 : : /*
1374 : : * MultiXactCutoff, similarly, shouldn't go backwards either.
1375 : : */
1376 : : {
1377 : 409 : MultiXactId relminmxid = OldHeap->rd_rel->relminmxid;
1378 : :
1379 [ + - - + ]: 818 : if (MultiXactIdIsValid(relminmxid) &&
1380 : 409 : MultiXactIdPrecedes(cutoffs.MultiXactCutoff, relminmxid))
850 noah@leadboat.com 1381 :UBC 0 : cutoffs.MultiXactCutoff = relminmxid;
1382 : : }
1383 : :
1384 : : /*
1385 : : * Decide whether to use an indexscan or seqscan-and-optional-sort to scan
1386 : : * the OldHeap. We know how to use a sort to duplicate the ordering of a
1387 : : * btree index, and will use seqscan-and-sort for that case if the planner
1388 : : * tells us it's cheaper. Otherwise, always indexscan if an index is
1389 : : * provided, else plain seqscan.
1390 : : */
5803 tgl@sss.pgh.pa.us 1391 [ + + + + ]:CBC 409 : if (OldIndex != NULL && OldIndex->rd_rel->relam == BTREE_AM_OID)
594 alvherre@alvh.no-ip. 1392 : 145 : use_sort = plan_cluster_use_sort(RelationGetRelid(OldHeap),
1393 : : RelationGetRelid(OldIndex));
1394 : : else
5803 tgl@sss.pgh.pa.us 1395 : 264 : use_sort = false;
1396 : :
1397 : : /* Log what we're doing */
2709 andres@anarazel.de 1398 [ + + + + ]: 409 : if (OldIndex != NULL && !use_sort)
5803 tgl@sss.pgh.pa.us 1399 [ - + ]: 62 : ereport(elevel,
1400 : : errmsg("repacking \"%s.%s\" using index scan on \"%s\"",
1401 : : nspname,
1402 : : RelationGetRelationName(OldHeap),
1403 : : RelationGetRelationName(OldIndex)));
2709 andres@anarazel.de 1404 [ + + ]: 347 : else if (use_sort)
5803 tgl@sss.pgh.pa.us 1405 [ - + ]: 85 : ereport(elevel,
1406 : : errmsg("repacking \"%s.%s\" using sequential scan and sort",
1407 : : nspname,
1408 : : RelationGetRelationName(OldHeap)));
1409 : : else
1410 [ + + ]: 262 : ereport(elevel,
1411 : : errmsg("repacking \"%s.%s\" in physical order",
1412 : : nspname,
1413 : : RelationGetRelationName(OldHeap)));
1414 : :
1415 : : /*
1416 : : * Hand off the actual copying to AM specific function, the generic code
1417 : : * cannot know how to deal with visibility across AMs. Note that this
1418 : : * routine is allowed to set FreezeXid / MultiXactCutoff to different
1419 : : * values (e.g. because the AM doesn't use freezing).
1420 : : */
2709 andres@anarazel.de 1421 : 409 : table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort,
1422 : : cutoffs.OldestXmin, snapshot,
1423 : : &cutoffs.FreezeLimit,
1424 : : &cutoffs.MultiXactCutoff,
1425 : : &num_tuples, &tups_vacuumed,
1426 : : &tups_recently_dead);
1427 : :
1428 : : /* return selected values to caller, get set as relfrozenxid/minmxid */
1344 pg@bowt.ie 1429 : 409 : *pFreezeXid = cutoffs.FreezeLimit;
1430 : 409 : *pCutoffMulti = cutoffs.MultiXactCutoff;
1431 : :
1432 : : /*
1433 : : * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again.
1434 : : * In the CONCURRENTLY case, we need to set it again before applying the
1435 : : * concurrent changes.
1436 : : */
6048 tgl@sss.pgh.pa.us 1437 : 409 : NewHeap->rd_toastoid = InvalidOid;
1438 : :
3165 teodor@sigaev.ru 1439 : 409 : num_pages = RelationGetNumberOfBlocks(NewHeap);
1440 : :
1441 : : /* Log what we did */
5803 tgl@sss.pgh.pa.us 1442 [ + + ]: 409 : ereport(elevel,
1443 : : (errmsg("\"%s.%s\": found %.0f removable, %.0f nonremovable row versions in %u pages",
1444 : : nspname,
1445 : : RelationGetRelationName(OldHeap),
1446 : : tups_vacuumed, num_tuples,
1447 : : RelationGetNumberOfBlocks(OldHeap)),
1448 : : errdetail("%.0f dead row versions cannot be removed yet.\n"
1449 : : "%s.",
1450 : : tups_recently_dead,
1451 : : pg_rusage_show(&ru0))));
1452 : :
1453 : : /* Update pg_class to reflect the correct values of pages and tuples. */
2775 andres@anarazel.de 1454 : 409 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1455 : :
594 alvherre@alvh.no-ip. 1456 : 409 : reltup = SearchSysCacheCopy1(RELOID,
1457 : : ObjectIdGetDatum(RelationGetRelid(NewHeap)));
3165 teodor@sigaev.ru 1458 [ - + ]: 409 : if (!HeapTupleIsValid(reltup))
594 alvherre@alvh.no-ip. 1459 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u",
1460 : : RelationGetRelid(NewHeap));
3165 teodor@sigaev.ru 1461 :CBC 409 : relform = (Form_pg_class) GETSTRUCT(reltup);
1462 : :
1463 : 409 : relform->relpages = num_pages;
1464 : 409 : relform->reltuples = num_tuples;
1465 : :
1466 : : /* Don't update the stats for pg_class. See swap_relation_files. */
594 alvherre@alvh.no-ip. 1467 [ + + ]: 409 : if (RelationGetRelid(OldHeap) != RelationRelationId)
3165 teodor@sigaev.ru 1468 : 386 : CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
1469 : : else
1470 : 23 : CacheInvalidateRelcacheByTuple(reltup);
1471 : :
1472 : : /* Clean up. */
1473 : 409 : heap_freetuple(reltup);
2775 andres@anarazel.de 1474 : 409 : table_close(relRelation, RowExclusiveLock);
1475 : :
1476 : : /* Make the update visible */
3165 teodor@sigaev.ru 1477 : 409 : CommandCounterIncrement();
11006 scrappy@hub.org 1478 : 409 : }
1479 : :
1480 : : /*
1481 : : * Swap the physical files of two given relations.
1482 : : *
1483 : : * We swap the physical identity (reltablespace, relfilenumber) while keeping
1484 : : * the same logical identities of the two relations. relpersistence is also
1485 : : * swapped, which is critical since it determines where buffers live for each
1486 : : * relation.
1487 : : *
1488 : : * We can swap associated TOAST data in either of two ways: recursively swap
1489 : : * the physical content of the toast tables (and their indexes), or swap the
1490 : : * TOAST links in the given relations' pg_class entries. The former is needed
1491 : : * to manage rewrites of shared catalogs (where we cannot change the pg_class
1492 : : * links) while the latter is the only way to handle cases in which a toast
1493 : : * table is added or removed altogether.
1494 : : *
1495 : : * Additionally, the first relation is marked with relfrozenxid set to
1496 : : * frozenXid. It seems a bit ugly to have this here, but the caller would
1497 : : * have to do it anyway, so having it here saves a heap_update. Note: in
1498 : : * the swap-toast-links case, we assume we don't need to change the toast
1499 : : * table's relfrozenxid: the new version of the toast table should already
1500 : : * have relfrozenxid set to RecentXmin, which is good enough.
1501 : : *
1502 : : * Lastly, if r2 and its toast table and toast index (if any) are mapped,
1503 : : * their OIDs are emitted into mapped_tables[]. This is hacky but beats
1504 : : * having to look the information up again later in finish_heap_swap.
1505 : : */
1506 : : static void
6045 tgl@sss.pgh.pa.us 1507 : 1723 : swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
1508 : : bool swap_toast_by_content,
1509 : : bool is_internal,
1510 : : TransactionId frozenXid,
1511 : : MultiXactId cutoffMulti,
1512 : : Oid *mapped_tables)
1513 : : {
1514 : : Relation relRelation;
1515 : : HeapTuple reltup1,
1516 : : reltup2;
1517 : : Form_pg_class relform1,
1518 : : relform2;
1519 : : RelFileNumber relfilenumber1,
1520 : : relfilenumber2;
1521 : : RelFileNumber swaptemp;
1522 : : char swptmpchr;
1523 : : Oid relam1,
1524 : : relam2;
1525 : :
1526 : : /* We need writable copies of both pg_class tuples. */
2775 andres@anarazel.de 1527 : 1723 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1528 : :
6038 rhaas@postgresql.org 1529 : 1723 : reltup1 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r1));
8782 tgl@sss.pgh.pa.us 1530 [ - + ]: 1723 : if (!HeapTupleIsValid(reltup1))
8439 tgl@sss.pgh.pa.us 1531 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", r1);
8782 tgl@sss.pgh.pa.us 1532 :CBC 1723 : relform1 = (Form_pg_class) GETSTRUCT(reltup1);
1533 : :
6038 rhaas@postgresql.org 1534 : 1723 : reltup2 = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(r2));
8782 tgl@sss.pgh.pa.us 1535 [ - + ]: 1723 : if (!HeapTupleIsValid(reltup2))
8439 tgl@sss.pgh.pa.us 1536 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", r2);
8782 tgl@sss.pgh.pa.us 1537 :CBC 1723 : relform2 = (Form_pg_class) GETSTRUCT(reltup2);
1538 : :
1513 rhaas@postgresql.org 1539 : 1723 : relfilenumber1 = relform1->relfilenode;
1540 : 1723 : relfilenumber2 = relform2->relfilenode;
1154 michael@paquier.xyz 1541 : 1723 : relam1 = relform1->relam;
1542 : 1723 : relam2 = relform2->relam;
1543 : :
1513 rhaas@postgresql.org 1544 [ + + + - ]: 1723 : if (RelFileNumberIsValid(relfilenumber1) &&
1545 : : RelFileNumberIsValid(relfilenumber2))
1546 : : {
1547 : : /*
1548 : : * Normal non-mapped relations: swap relfilenumbers, reltablespaces,
1549 : : * relpersistence
1550 : : */
6045 tgl@sss.pgh.pa.us 1551 [ - + ]: 1634 : Assert(!target_is_pg_class);
1552 : :
1553 : 1634 : swaptemp = relform1->relfilenode;
1554 : 1634 : relform1->relfilenode = relform2->relfilenode;
1555 : 1634 : relform2->relfilenode = swaptemp;
1556 : :
1557 : 1634 : swaptemp = relform1->reltablespace;
1558 : 1634 : relform1->reltablespace = relform2->reltablespace;
1559 : 1634 : relform2->reltablespace = swaptemp;
1560 : :
1856 michael@paquier.xyz 1561 : 1634 : swaptemp = relform1->relam;
1562 : 1634 : relform1->relam = relform2->relam;
1563 : 1634 : relform2->relam = swaptemp;
1564 : :
4388 alvherre@alvh.no-ip. 1565 : 1634 : swptmpchr = relform1->relpersistence;
1566 : 1634 : relform1->relpersistence = relform2->relpersistence;
1567 : 1634 : relform2->relpersistence = swptmpchr;
1568 : :
1569 : : /* Also swap toast links, if we're swapping by links */
6045 tgl@sss.pgh.pa.us 1570 [ + + ]: 1634 : if (!swap_toast_by_content)
1571 : : {
1572 : 1298 : swaptemp = relform1->reltoastrelid;
1573 : 1298 : relform1->reltoastrelid = relform2->reltoastrelid;
1574 : 1298 : relform2->reltoastrelid = swaptemp;
1575 : : }
1576 : : }
1577 : : else
1578 : : {
1579 : : /*
1580 : : * Mapped-relation case. Here we have to swap the relation mappings
1581 : : * instead of modifying the pg_class columns. Both must be mapped.
1582 : : */
1513 rhaas@postgresql.org 1583 [ + - - + ]: 89 : if (RelFileNumberIsValid(relfilenumber1) ||
1584 : : RelFileNumberIsValid(relfilenumber2))
6045 tgl@sss.pgh.pa.us 1585 [ # # ]:UBC 0 : elog(ERROR, "cannot swap mapped relation \"%s\" with non-mapped relation",
1586 : : NameStr(relform1->relname));
1587 : :
1588 : : /*
1589 : : * We can't change the tablespace nor persistence of a mapped rel, and
1590 : : * we can't handle toast link swapping for one either, because we must
1591 : : * not apply any critical changes to its pg_class row. These cases
1592 : : * should be prevented by upstream permissions tests, so these checks
1593 : : * are non-user-facing emergency backstop.
1594 : : */
6045 tgl@sss.pgh.pa.us 1595 [ - + ]:CBC 89 : if (relform1->reltablespace != relform2->reltablespace)
6045 tgl@sss.pgh.pa.us 1596 [ # # ]:UBC 0 : elog(ERROR, "cannot change tablespace of mapped relation \"%s\"",
1597 : : NameStr(relform1->relname));
4388 alvherre@alvh.no-ip. 1598 [ - + ]:CBC 89 : if (relform1->relpersistence != relform2->relpersistence)
4388 alvherre@alvh.no-ip. 1599 [ # # ]:UBC 0 : elog(ERROR, "cannot change persistence of mapped relation \"%s\"",
1600 : : NameStr(relform1->relname));
1856 michael@paquier.xyz 1601 [ - + ]:CBC 89 : if (relform1->relam != relform2->relam)
1856 michael@paquier.xyz 1602 [ # # ]:UBC 0 : elog(ERROR, "cannot change access method of mapped relation \"%s\"",
1603 : : NameStr(relform1->relname));
6045 tgl@sss.pgh.pa.us 1604 [ + + ]:CBC 89 : if (!swap_toast_by_content &&
1605 [ + - - + ]: 29 : (relform1->reltoastrelid || relform2->reltoastrelid))
6045 tgl@sss.pgh.pa.us 1606 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast by links for mapped relation \"%s\"",
1607 : : NameStr(relform1->relname));
1608 : :
1609 : : /*
1610 : : * Fetch the mappings --- shouldn't fail, but be paranoid
1611 : : */
1513 rhaas@postgresql.org 1612 :CBC 89 : relfilenumber1 = RelationMapOidToFilenumber(r1, relform1->relisshared);
1613 [ - + ]: 89 : if (!RelFileNumberIsValid(relfilenumber1))
6045 tgl@sss.pgh.pa.us 1614 [ # # ]:UBC 0 : elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
1615 : : NameStr(relform1->relname), r1);
1513 rhaas@postgresql.org 1616 :CBC 89 : relfilenumber2 = RelationMapOidToFilenumber(r2, relform2->relisshared);
1617 [ - + ]: 89 : if (!RelFileNumberIsValid(relfilenumber2))
6045 tgl@sss.pgh.pa.us 1618 [ # # ]:UBC 0 : elog(ERROR, "could not find relation mapping for relation \"%s\", OID %u",
1619 : : NameStr(relform2->relname), r2);
1620 : :
1621 : : /*
1622 : : * Send replacement mappings to relmapper. Note these won't actually
1623 : : * take effect until CommandCounterIncrement.
1624 : : */
1513 rhaas@postgresql.org 1625 :CBC 89 : RelationMapUpdateMap(r1, relfilenumber2, relform1->relisshared, false);
1626 : 89 : RelationMapUpdateMap(r2, relfilenumber1, relform2->relisshared, false);
1627 : :
1628 : : /* Pass OIDs of mapped r2 tables back to caller */
6045 tgl@sss.pgh.pa.us 1629 : 89 : *mapped_tables++ = r2;
1630 : : }
1631 : :
1632 : : /*
1633 : : * Recognize that rel1's relfilenumber (swapped from rel2) is new in this
1634 : : * subtransaction. The rel2 storage (swapped from rel1) may or may not be
1635 : : * new.
1636 : : */
1637 : : {
1638 : : Relation rel1,
1639 : : rel2;
1640 : :
2336 noah@leadboat.com 1641 : 1723 : rel1 = relation_open(r1, NoLock);
1642 : 1723 : rel2 = relation_open(r2, NoLock);
1643 : 1723 : rel2->rd_createSubid = rel1->rd_createSubid;
1513 rhaas@postgresql.org 1644 : 1723 : rel2->rd_newRelfilelocatorSubid = rel1->rd_newRelfilelocatorSubid;
1645 : 1723 : rel2->rd_firstRelfilelocatorSubid = rel1->rd_firstRelfilelocatorSubid;
1646 : 1723 : RelationAssumeNewRelfilelocator(rel1);
2336 noah@leadboat.com 1647 : 1723 : relation_close(rel1, NoLock);
1648 : 1723 : relation_close(rel2, NoLock);
1649 : : }
1650 : :
1651 : : /*
1652 : : * In the case of a shared catalog, these next few steps will only affect
1653 : : * our own database's pg_class row; but that's okay, because they are all
1654 : : * noncritical updates. That's also an important fact for the case of a
1655 : : * mapped catalog, because it's possible that we'll commit the map change
1656 : : * and then fail to commit the pg_class update.
1657 : : */
1658 : :
1659 : : /* set rel1's frozen Xid and minimum MultiXid */
6048 tgl@sss.pgh.pa.us 1660 [ + + ]: 1723 : if (relform1->relkind != RELKIND_INDEX)
1661 : : {
2683 andres@anarazel.de 1662 [ + - - + ]: 1583 : Assert(!TransactionIdIsValid(frozenXid) ||
1663 : : TransactionIdIsNormal(frozenXid));
6048 tgl@sss.pgh.pa.us 1664 : 1583 : relform1->relfrozenxid = frozenXid;
4728 alvherre@alvh.no-ip. 1665 : 1583 : relform1->relminmxid = cutoffMulti;
1666 : : }
1667 : :
1668 : : /* swap size statistics too, since new rel has freshly-updated stats */
1669 : : {
1670 : : int32 swap_pages;
1671 : : float4 swap_tuples;
1672 : : int32 swap_allvisible;
1673 : : int32 swap_allfrozen;
1674 : :
8699 tgl@sss.pgh.pa.us 1675 : 1723 : swap_pages = relform1->relpages;
1676 : 1723 : relform1->relpages = relform2->relpages;
1677 : 1723 : relform2->relpages = swap_pages;
1678 : :
1679 : 1723 : swap_tuples = relform1->reltuples;
1680 : 1723 : relform1->reltuples = relform2->reltuples;
1681 : 1723 : relform2->reltuples = swap_tuples;
1682 : :
5431 1683 : 1723 : swap_allvisible = relform1->relallvisible;
1684 : 1723 : relform1->relallvisible = relform2->relallvisible;
1685 : 1723 : relform2->relallvisible = swap_allvisible;
1686 : :
542 melanieplageman@gmai 1687 : 1723 : swap_allfrozen = relform1->relallfrozen;
1688 : 1723 : relform1->relallfrozen = relform2->relallfrozen;
1689 : 1723 : relform2->relallfrozen = swap_allfrozen;
1690 : : }
1691 : :
1692 : : /*
1693 : : * Update the tuples in pg_class --- unless the target relation of the
1694 : : * swap is pg_class itself. In that case, there is zero point in making
1695 : : * changes because we'd be updating the old data that we're about to throw
1696 : : * away. Because the real work being done here for a mapped relation is
1697 : : * just to change the relation map settings, it's all right to not update
1698 : : * the pg_class rows in this case. The most important changes will instead
1699 : : * performed later, in finish_heap_swap() itself.
1700 : : */
6045 tgl@sss.pgh.pa.us 1701 [ + + ]: 1723 : if (!target_is_pg_class)
1702 : : {
1703 : : CatalogIndexState indstate;
1704 : :
1705 : 1700 : indstate = CatalogOpenIndexes(relRelation);
3494 1706 : 1700 : CatalogTupleUpdateWithInfo(relRelation, &reltup1->t_self, reltup1,
1707 : : indstate);
1708 : 1700 : CatalogTupleUpdateWithInfo(relRelation, &reltup2->t_self, reltup2,
1709 : : indstate);
6045 1710 : 1700 : CatalogCloseIndexes(indstate);
1711 : : }
1712 : : else
1713 : : {
1714 : : /* no update ... but we do still need relcache inval */
1715 : 23 : CacheInvalidateRelcacheByTuple(reltup1);
1716 : 23 : CacheInvalidateRelcacheByTuple(reltup2);
1717 : : }
1718 : :
1719 : : /*
1720 : : * Now that pg_class has been updated with its relevant information for
1721 : : * the swap, update the dependency of the relations to point to their new
1722 : : * table AM, if it has changed.
1723 : : */
1154 michael@paquier.xyz 1724 [ + + ]: 1723 : if (relam1 != relam2)
1725 : : {
1726 [ - + ]: 24 : if (changeDependencyFor(RelationRelationId,
1727 : : r1,
1728 : : AccessMethodRelationId,
1729 : : relam1,
1730 : : relam2) != 1)
1144 michael@paquier.xyz 1731 [ # # ]:UBC 0 : elog(ERROR, "could not change access method dependency for relation \"%s.%s\"",
1732 : : get_namespace_name(get_rel_namespace(r1)),
1733 : : get_rel_name(r1));
1154 michael@paquier.xyz 1734 [ - + ]:CBC 24 : if (changeDependencyFor(RelationRelationId,
1735 : : r2,
1736 : : AccessMethodRelationId,
1737 : : relam2,
1738 : : relam1) != 1)
1144 michael@paquier.xyz 1739 [ # # ]:UBC 0 : elog(ERROR, "could not change access method dependency for relation \"%s.%s\"",
1740 : : get_namespace_name(get_rel_namespace(r2)),
1741 : : get_rel_name(r2));
1742 : : }
1743 : :
1744 : : /*
1745 : : * Post alter hook for modified relations. The change to r2 is always
1746 : : * internal, but r1 depends on the invocation context.
1747 : : */
4911 rhaas@postgresql.org 1748 [ - + ]:CBC 1723 : InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
1749 : : InvalidOid, is_internal);
1750 [ - + ]: 1723 : InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
1751 : : InvalidOid, true);
1752 : :
1753 : : /*
1754 : : * If we have toast tables associated with the relations being swapped,
1755 : : * deal with them too.
1756 : : */
8782 tgl@sss.pgh.pa.us 1757 [ + + + + ]: 1723 : if (relform1->reltoastrelid || relform2->reltoastrelid)
1758 : : {
6048 1759 [ + + ]: 542 : if (swap_toast_by_content)
1760 : : {
1761 [ + - + - ]: 132 : if (relform1->reltoastrelid && relform2->reltoastrelid)
1762 : : {
1763 : : /* Recursively swap the contents of the toast tables */
1764 : 132 : swap_relation_files(relform1->reltoastrelid,
1765 : : relform2->reltoastrelid,
1766 : : target_is_pg_class,
1767 : : swap_toast_by_content,
1768 : : is_internal,
1769 : : frozenXid,
1770 : : cutoffMulti,
1771 : : mapped_tables);
1772 : : }
1773 : : else
1774 : : {
1775 : : /* caller messed up */
6048 tgl@sss.pgh.pa.us 1776 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast files by content when there's only one");
1777 : : }
1778 : : }
1779 : : else
1780 : : {
1781 : : /*
1782 : : * We swapped the ownership links, so we need to change dependency
1783 : : * data to match.
1784 : : *
1785 : : * NOTE: it is possible that only one table has a toast table.
1786 : : *
1787 : : * NOTE: at present, a TOAST table's only dependency is the one on
1788 : : * its owning table. If more are ever created, we'd need to use
1789 : : * something more selective than deleteDependencyRecordsFor() to
1790 : : * get rid of just the link we want.
1791 : : */
1792 : : ObjectAddress baseobject,
1793 : : toastobject;
1794 : : long count;
1795 : :
1796 : : /*
1797 : : * We disallow this case for system catalogs, to avoid the
1798 : : * possibility that the catalog we're rebuilding is one of the
1799 : : * ones the dependency changes would change. It's too late to be
1800 : : * making any data changes to the target catalog.
1801 : : */
4655 rhaas@postgresql.org 1802 [ - + ]:CBC 410 : if (IsSystemClass(r1, relform1))
6045 tgl@sss.pgh.pa.us 1803 [ # # ]:UBC 0 : elog(ERROR, "cannot swap toast files by links for system catalogs");
1804 : :
1805 : : /* Delete old dependencies */
6048 tgl@sss.pgh.pa.us 1806 [ + + ]:CBC 410 : if (relform1->reltoastrelid)
1807 : : {
1808 : 389 : count = deleteDependencyRecordsFor(RelationRelationId,
1809 : : relform1->reltoastrelid,
1810 : : false);
1811 [ - + ]: 389 : if (count != 1)
6048 tgl@sss.pgh.pa.us 1812 [ # # ]:UBC 0 : elog(ERROR, "expected one dependency record for TOAST table, found %ld",
1813 : : count);
1814 : : }
6048 tgl@sss.pgh.pa.us 1815 [ + - ]:CBC 410 : if (relform2->reltoastrelid)
1816 : : {
1817 : 410 : count = deleteDependencyRecordsFor(RelationRelationId,
1818 : : relform2->reltoastrelid,
1819 : : false);
1820 [ - + ]: 410 : if (count != 1)
6048 tgl@sss.pgh.pa.us 1821 [ # # ]:UBC 0 : elog(ERROR, "expected one dependency record for TOAST table, found %ld",
1822 : : count);
1823 : : }
1824 : :
1825 : : /* Register new dependencies */
6048 tgl@sss.pgh.pa.us 1826 :CBC 410 : baseobject.classId = RelationRelationId;
1827 : 410 : baseobject.objectSubId = 0;
1828 : 410 : toastobject.classId = RelationRelationId;
1829 : 410 : toastobject.objectSubId = 0;
1830 : :
1831 [ + + ]: 410 : if (relform1->reltoastrelid)
1832 : : {
1833 : 389 : baseobject.objectId = r1;
1834 : 389 : toastobject.objectId = relform1->reltoastrelid;
1835 : 389 : recordDependencyOn(&toastobject, &baseobject,
1836 : : DEPENDENCY_INTERNAL);
1837 : : }
1838 : :
1839 [ + - ]: 410 : if (relform2->reltoastrelid)
1840 : : {
1841 : 410 : baseobject.objectId = r2;
1842 : 410 : toastobject.objectId = relform2->reltoastrelid;
1843 : 410 : recordDependencyOn(&toastobject, &baseobject,
1844 : : DEPENDENCY_INTERNAL);
1845 : : }
1846 : : }
1847 : : }
1848 : :
1849 : : /*
1850 : : * If we're swapping two toast tables by content, do the same for their
1851 : : * valid index. The swap can actually be safely done only if the relations
1852 : : * have indexes.
1853 : : */
1854 [ + + ]: 1723 : if (swap_toast_by_content &&
4802 fujii@postgresql.org 1855 [ + + ]: 396 : relform1->relkind == RELKIND_TOASTVALUE &&
1856 [ + - ]: 132 : relform2->relkind == RELKIND_TOASTVALUE)
1857 : : {
1858 : : Oid toastIndex1,
1859 : : toastIndex2;
1860 : :
1861 : : /* Get valid index for each relation */
1862 : 132 : toastIndex1 = toast_get_valid_index(r1,
1863 : : AccessExclusiveLock);
1864 : 132 : toastIndex2 = toast_get_valid_index(r2,
1865 : : AccessExclusiveLock);
1866 : :
1867 : 132 : swap_relation_files(toastIndex1,
1868 : : toastIndex2,
1869 : : target_is_pg_class,
1870 : : swap_toast_by_content,
1871 : : is_internal,
1872 : : InvalidTransactionId,
1873 : : InvalidMultiXactId,
1874 : : mapped_tables);
1875 : : }
1876 : :
1877 : : /* Clean up. */
8782 tgl@sss.pgh.pa.us 1878 : 1723 : heap_freetuple(reltup1);
1879 : 1723 : heap_freetuple(reltup2);
1880 : :
2775 andres@anarazel.de 1881 : 1723 : table_close(relRelation, RowExclusiveLock);
8783 bruce@momjian.us 1882 : 1723 : }
1883 : :
1884 : : /*
1885 : : * Remove the transient table that was built by make_new_heap, and finish
1886 : : * cleaning up (including rebuilding all indexes on the old heap).
1887 : : */
1888 : : void
6045 tgl@sss.pgh.pa.us 1889 : 1451 : finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
1890 : : bool is_system_catalog,
1891 : : bool swap_toast_by_content,
1892 : : bool check_constraints,
1893 : : bool is_internal,
1894 : : bool reindex,
1895 : : TransactionId frozenXid,
1896 : : MultiXactId cutoffMulti,
1897 : : char newrelpersistence)
1898 : : {
1899 : : ObjectAddress object;
1900 : : Oid mapped_tables[4];
1901 : : int i;
1902 : :
1903 : : /* Report that we are now swapping relation files */
170 alvherre@kurilemu.de 1904 : 1451 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
1905 : : PROGRESS_REPACK_PHASE_SWAP_REL_FILES);
1906 : :
1907 : : /* Zero out possible results from swapped_relation_files */
6045 tgl@sss.pgh.pa.us 1908 : 1451 : memset(mapped_tables, 0, sizeof(mapped_tables));
1909 : :
1910 : : /*
1911 : : * Swap the contents of the heap relations (including any toast tables).
1912 : : * Also set old heap's relfrozenxid to frozenXid.
1913 : : */
1914 : 1451 : swap_relation_files(OIDOldHeap, OIDNewHeap,
1915 : : (OIDOldHeap == RelationRelationId),
1916 : : swap_toast_by_content, is_internal,
1917 : : frozenXid, cutoffMulti, mapped_tables);
1918 : :
1919 : : /*
1920 : : * If it's a system catalog, queue a sinval message to flush all catcaches
1921 : : * on the catalog when we reach CommandCounterIncrement.
1922 : : */
1923 [ + + ]: 1451 : if (is_system_catalog)
1924 : 121 : CacheInvalidateCatalog(OIDOldHeap);
1925 : :
143 alvherre@kurilemu.de 1926 [ + + ]: 1451 : if (reindex)
1927 : : {
1928 : : int reindex_flags;
1929 : 1444 : ReindexParams reindex_params = {0};
1930 : :
1931 : : /*
1932 : : * Rebuild each index on the relation (but not the toast table, which
1933 : : * is all-new at this point). It is important to do this before the
1934 : : * DROP step because if we are processing a system catalog that will
1935 : : * be used during DROP, we want to have its indexes available. There
1936 : : * is no advantage to the other order anyway because this is all
1937 : : * transactional, so no chance to reclaim disk space before commit. We
1938 : : * do not need a final CommandCounterIncrement() because
1939 : : * reindex_relation does it.
1940 : : *
1941 : : * Note: because index_build is called via reindex_relation, it will
1942 : : * never set indcheckxmin true for the indexes. This is OK even
1943 : : * though in some sense we are building new indexes rather than
1944 : : * rebuilding existing ones, because the new heap won't contain any
1945 : : * HOT chains at all, let alone broken ones, so it can't be necessary
1946 : : * to set indcheckxmin.
1947 : : */
1948 : 1444 : reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE;
1949 [ + + ]: 1444 : if (check_constraints)
1950 : 1042 : reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS;
1951 : :
1952 : : /*
1953 : : * Ensure that the indexes have the same persistence as the parent
1954 : : * relation.
1955 : : */
1956 [ + + ]: 1444 : if (newrelpersistence == RELPERSISTENCE_UNLOGGED)
1957 : 25 : reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
1958 [ + + ]: 1419 : else if (newrelpersistence == RELPERSISTENCE_PERMANENT)
1959 : 1366 : reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
1960 : :
1961 : : /* Report that we are now reindexing relations */
1962 : 1444 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
1963 : : PROGRESS_REPACK_PHASE_REBUILD_INDEX);
1964 : :
1965 : 1444 : reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params);
1966 : : }
1967 : :
1968 : : /* Report that we are now doing clean up */
170 1969 : 1439 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
1970 : : PROGRESS_REPACK_PHASE_FINAL_CLEANUP);
1971 : :
1972 : : /*
1973 : : * If the relation being rebuilt is pg_class, swap_relation_files()
1974 : : * couldn't update pg_class's own pg_class entry (check comments in
1975 : : * swap_relation_files()), thus relfrozenxid was not updated. That's
1976 : : * annoying because a potential reason for doing a VACUUM FULL is a
1977 : : * imminent or actual anti-wraparound shutdown. So, now that we can
1978 : : * access the new relation using its indices, update relfrozenxid.
1979 : : * pg_class doesn't have a toast relation, so we don't need to update the
1980 : : * corresponding toast relation. Not that there's little point moving all
1981 : : * relfrozenxid updates here since swap_relation_files() needs to write to
1982 : : * pg_class for non-mapped relations anyway.
1983 : : */
4559 rhaas@postgresql.org 1984 [ + + ]: 1439 : if (OIDOldHeap == RelationRelationId)
1985 : : {
1986 : : Relation relRelation;
1987 : : HeapTuple reltup;
1988 : : Form_pg_class relform;
1989 : :
2775 andres@anarazel.de 1990 : 23 : relRelation = table_open(RelationRelationId, RowExclusiveLock);
1991 : :
4559 rhaas@postgresql.org 1992 : 23 : reltup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(OIDOldHeap));
1993 [ - + ]: 23 : if (!HeapTupleIsValid(reltup))
4559 rhaas@postgresql.org 1994 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", OIDOldHeap);
4559 rhaas@postgresql.org 1995 :CBC 23 : relform = (Form_pg_class) GETSTRUCT(reltup);
1996 : :
1997 : 23 : relform->relfrozenxid = frozenXid;
1998 : 23 : relform->relminmxid = cutoffMulti;
1999 : :
3495 alvherre@alvh.no-ip. 2000 : 23 : CatalogTupleUpdate(relRelation, &reltup->t_self, reltup);
2001 : :
2775 andres@anarazel.de 2002 : 23 : table_close(relRelation, RowExclusiveLock);
2003 : : }
2004 : :
2005 : : /* Destroy new heap with old filenumber */
6048 tgl@sss.pgh.pa.us 2006 : 1439 : object.classId = RelationRelationId;
2007 : 1439 : object.objectId = OIDNewHeap;
2008 : 1439 : object.objectSubId = 0;
2009 : :
143 alvherre@kurilemu.de 2010 [ + + ]: 1439 : if (!reindex)
2011 : : {
2012 : : /*
2013 : : * Make sure the changes in pg_class are visible. This is especially
2014 : : * important if !swap_toast_by_content, so that the correct TOAST
2015 : : * relation is dropped. (reindex_relation() above did not help in this
2016 : : * case))
2017 : : */
2018 : 7 : CommandCounterIncrement();
2019 : : }
2020 : :
2021 : : /*
2022 : : * The new relation is local to our transaction and we know nothing
2023 : : * depends on it, so DROP_RESTRICT should be OK.
2024 : : */
5327 rhaas@postgresql.org 2025 : 1439 : performDeletion(&object, DROP_RESTRICT, PERFORM_DELETION_INTERNAL);
2026 : :
2027 : : /* performDeletion does CommandCounterIncrement at end */
2028 : :
2029 : : /*
2030 : : * Now we must remove any relation mapping entries that we set up for the
2031 : : * transient table, as well as its toast table and toast index if any. If
2032 : : * we fail to do this before commit, the relmapper will complain about new
2033 : : * permanent map entries being added post-bootstrap.
2034 : : */
6045 tgl@sss.pgh.pa.us 2035 [ + + ]: 1528 : for (i = 0; OidIsValid(mapped_tables[i]); i++)
2036 : 89 : RelationMapRemoveMapping(mapped_tables[i]);
2037 : :
2038 : : /*
2039 : : * At this point, everything is kosher except that, if we did toast swap
2040 : : * by links, the toast table's name corresponds to the transient table.
2041 : : * The name is irrelevant to the backend because it's referenced by OID,
2042 : : * but users looking at the catalogs could be confused. Rename it to
2043 : : * prevent this problem.
2044 : : *
2045 : : * Note no lock required on the relation, because we already hold an
2046 : : * exclusive lock on it.
2047 : : */
6048 2048 [ + + ]: 1439 : if (!swap_toast_by_content)
2049 : : {
2050 : : Relation newrel;
2051 : :
2775 andres@anarazel.de 2052 : 1307 : newrel = table_open(OIDOldHeap, NoLock);
6048 tgl@sss.pgh.pa.us 2053 [ + + ]: 1307 : if (OidIsValid(newrel->rd_rel->reltoastrelid))
2054 : : {
2055 : : Oid toastidx;
2056 : : char NewToastName[NAMEDATALEN];
2057 : :
2058 : : /* Get the associated valid index to be renamed */
4802 fujii@postgresql.org 2059 : 389 : toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid,
2060 : : AccessExclusiveLock);
2061 : :
2062 : : /* rename the toast table ... */
6048 tgl@sss.pgh.pa.us 2063 : 389 : snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u",
2064 : : OIDOldHeap);
2065 : 389 : RenameRelationInternal(newrel->rd_rel->reltoastrelid,
2066 : : NewToastName, true, false);
2067 : :
2068 : : /* ... and its valid index too. */
2069 : 389 : snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u_index",
2070 : : OIDOldHeap);
2071 : :
2072 : 389 : RenameRelationInternal(toastidx,
2073 : : NewToastName, true, true);
2074 : :
2075 : : /*
2076 : : * Reset the relrewrite for the toast. The command-counter
2077 : : * increment is required here as we are about to update the tuple
2078 : : * that is updated as part of RenameRelationInternal.
2079 : : */
1828 akapila@postgresql.o 2080 : 389 : CommandCounterIncrement();
2081 : 389 : ResetRelRewrite(newrel->rd_rel->reltoastrelid);
2082 : : }
6048 tgl@sss.pgh.pa.us 2083 : 1307 : relation_close(newrel, NoLock);
2084 : : }
2085 : :
2086 : : /* if it's not a catalog table, clear any missing attribute settings */
3074 andrew@dunslane.net 2087 [ + + ]: 1439 : if (!is_system_catalog)
2088 : : {
2089 : : Relation newrel;
2090 : :
2775 andres@anarazel.de 2091 : 1318 : newrel = table_open(OIDOldHeap, NoLock);
3074 andrew@dunslane.net 2092 : 1318 : RelationClearMissing(newrel);
2093 : 1318 : relation_close(newrel, NoLock);
2094 : : }
6048 tgl@sss.pgh.pa.us 2095 : 1439 : }
2096 : :
2097 : : /*
2098 : : * Determine which relations to process, when REPACK/CLUSTER is called
2099 : : * without specifying a table name. The exact process depends on whether
2100 : : * USING INDEX was given or not, and in any case we only return tables and
2101 : : * materialized views that the current user has privileges to repack/cluster.
2102 : : *
2103 : : * If USING INDEX was given, we scan pg_index to find those that have
2104 : : * indisclustered set; if it was not given, scan pg_class and return all
2105 : : * tables.
2106 : : *
2107 : : * Return it as a list of RelToCluster in the given memory context.
2108 : : */
2109 : : static List *
170 alvherre@kurilemu.de 2110 : 16 : get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
2111 : : {
2112 : : Relation catalog;
2113 : : TableScanDesc scan;
2114 : : HeapTuple tuple;
1608 alvherre@alvh.no-ip. 2115 : 16 : List *rtcs = NIL;
2116 : :
170 alvherre@kurilemu.de 2117 [ + + ]: 16 : if (usingindex)
2118 : : {
2119 : : ScanKeyData entry;
2120 : :
2121 : : /*
2122 : : * For USING INDEX, scan pg_index to find those with indisclustered.
2123 : : *
2124 : : * Note we don't obtain lock of any kind on the index, which means the
2125 : : * index or its owning table could be gone or change at any point. We
2126 : : * have to be extra careful when examining catalog state for them.
2127 : : */
2128 : 12 : catalog = table_open(IndexRelationId, AccessShareLock);
2129 : 12 : ScanKeyInit(&entry,
2130 : : Anum_pg_index_indisclustered,
2131 : : BTEqualStrategyNumber, F_BOOLEQ,
2132 : : BoolGetDatum(true));
2133 : 12 : scan = table_beginscan_catalog(catalog, 1, &entry);
2134 [ + + ]: 24 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
2135 : : {
2136 : : RelToCluster *rtc;
2137 : : Form_pg_index index;
2138 : : HeapTuple classtup;
2139 : : Oid relnamespace;
2140 : : char relpersistence;
2141 : : MemoryContext oldcxt;
2142 : :
2143 : 12 : index = (Form_pg_index) GETSTRUCT(tuple);
2144 : :
114 2145 : 12 : classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
2146 [ - + ]: 12 : if (!HeapTupleIsValid(classtup))
170 alvherre@kurilemu.de 2147 :UBC 0 : continue;
48 alvherre@kurilemu.de 2148 :CBC 12 : relnamespace = ((Form_pg_class) GETSTRUCT(classtup))->relnamespace;
2149 : 12 : relpersistence = ((Form_pg_class) GETSTRUCT(classtup))->relpersistence;
2150 : 12 : ReleaseSysCache(classtup);
2151 : :
2152 : : /* Skip temp relations belonging to other sessions */
2153 [ - + ]: 12 : if (relpersistence == RELPERSISTENCE_TEMP &&
48 alvherre@kurilemu.de 2154 [ # # ]:UBC 0 : !isTempOrTempToastNamespace(relnamespace))
114 2155 : 0 : continue;
2156 : :
2157 : : /* noisily skip rels which the user can't process */
170 alvherre@kurilemu.de 2158 [ + + ]:CBC 12 : if (!repack_is_permitted_for_relation(cmd, index->indrelid,
2159 : : GetUserId(), false))
2160 : 8 : continue;
2161 : :
2162 : : /* Use a permanent memory context for the result list */
2163 : 4 : oldcxt = MemoryContextSwitchTo(permcxt);
2164 : 4 : rtc = palloc_object(RelToCluster);
2165 : 4 : rtc->tableOid = index->indrelid;
2166 : 4 : rtc->indexOid = index->indexrelid;
2167 : 4 : rtcs = lappend(rtcs, rtc);
2168 : 4 : MemoryContextSwitchTo(oldcxt);
2169 : : }
2170 : : }
2171 : : else
2172 : : {
2173 : 4 : catalog = table_open(RelationRelationId, AccessShareLock);
2174 : 4 : scan = table_beginscan_catalog(catalog, 0, NULL);
2175 : :
2176 [ + + ]: 9228 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
2177 : : {
2178 : : RelToCluster *rtc;
2179 : : Form_pg_class class;
2180 : : MemoryContext oldcxt;
2181 : :
2182 : 9224 : class = (Form_pg_class) GETSTRUCT(tuple);
2183 : :
2184 : : /* Can only process plain tables and matviews */
2185 [ + + ]: 9224 : if (class->relkind != RELKIND_RELATION &&
2186 [ + + ]: 6120 : class->relkind != RELKIND_MATVIEW)
2187 : 6088 : continue;
2188 : :
2189 : : /* Skip temp relations belonging to other sessions */
114 2190 [ + + ]: 3136 : if (class->relpersistence == RELPERSISTENCE_TEMP &&
2191 [ - + ]: 16 : !isTempOrTempToastNamespace(class->relnamespace))
114 alvherre@kurilemu.de 2192 :UBC 0 : continue;
2193 : :
2194 : : /* noisily skip rels which the user can't process */
170 alvherre@kurilemu.de 2195 [ + + ]:CBC 3136 : if (!repack_is_permitted_for_relation(cmd, class->oid,
2196 : : GetUserId(), false))
2197 : 3128 : continue;
2198 : :
2199 : : /* Use a permanent memory context for the result list */
2200 : 8 : oldcxt = MemoryContextSwitchTo(permcxt);
2201 : 8 : rtc = palloc_object(RelToCluster);
2202 : 8 : rtc->tableOid = class->oid;
2203 : 8 : rtc->indexOid = InvalidOid;
2204 : 8 : rtcs = lappend(rtcs, rtc);
2205 : 8 : MemoryContextSwitchTo(oldcxt);
2206 : : }
2207 : : }
2208 : :
2209 : 16 : table_endscan(scan);
48 2210 : 16 : table_close(catalog, AccessShareLock);
2211 : :
1608 alvherre@alvh.no-ip. 2212 : 16 : return rtcs;
2213 : : }
2214 : :
2215 : : /*
2216 : : * Determine relations to process, when REPACK/CLUSTER is called with a
2217 : : * partitioning table; that is, a list of its leaf partitions. That table has
2218 : : * already been opened by caller and is passed as 'rel'. It is closed and
2219 : : * unlocked here before return, so caller should clobber its pointer to avoid
2220 : : * confusion.
2221 : : *
2222 : : * Return it as a list of RelToCluster.
2223 : : *
2224 : : * XXX we don't support CONCURRENTLY for partitioned tables yet.
2225 : : */
2226 : : static List *
38 alvherre@kurilemu.de 2227 : 32 : get_tables_to_repack_partitioned(RepackStmt *stmt, Relation rel,
2228 : : MemoryContext permcxt)
2229 : : {
2230 : : Oid relid;
2231 : : bool rel_is_index;
2232 : : List *inhoids;
1608 alvherre@alvh.no-ip. 2233 : 32 : List *rtcs = NIL;
2234 : :
38 alvherre@kurilemu.de 2235 [ - + ]: 32 : Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
2236 [ - + ]: 32 : Assert(CheckRelationLockedByMe(rel, AccessExclusiveLock, false));
2237 : :
2238 : : /*
2239 : : * We find the list of tables by looking for inheritors. If USING INDEX
2240 : : * was given, look for inheritors of that index, whose name we resolve
2241 : : * now.
2242 : : *
2243 : : * Otherwise we look for inheritors of the table itself.
2244 : : */
2245 [ + + ]: 32 : if (stmt->usingindex)
2246 : : {
2247 : : /*
2248 : : * If no index name was specified when repacking a partitioned table,
2249 : : * punt for now. Maybe we can improve this later.
2250 : : */
2251 [ + + ]: 28 : if (!stmt->indexname)
2252 : : {
2253 [ + + ]: 8 : if (stmt->command == REPACK_COMMAND_CLUSTER)
2254 [ + - ]: 4 : ereport(ERROR,
2255 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2256 : : errmsg("there is no previously clustered index for table \"%s\"",
2257 : : RelationGetRelationName(rel)));
2258 : : else
2259 [ + - ]: 4 : ereport(ERROR,
2260 : : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
2261 : : /*- translator: first %s is name of a SQL command, eg. REPACK */
2262 : : errmsg("cannot execute %s on partitioned table \"%s\" USING INDEX with no index name",
2263 : : RepackCommandAsString(stmt->command),
2264 : : RelationGetRelationName(rel)));
2265 : : }
2266 : :
2267 : 20 : relid = determine_clustered_index(rel, stmt->usingindex,
2268 : 20 : stmt->indexname);
2269 [ - + ]: 20 : if (!OidIsValid(relid))
38 alvherre@kurilemu.de 2270 [ # # ]:UBC 0 : elog(ERROR, "unable to determine index to cluster on");
38 alvherre@kurilemu.de 2271 :CBC 20 : check_index_is_clusterable(rel, relid, AccessExclusiveLock);
2272 : :
2273 : 16 : rel_is_index = true;
2274 : : }
2275 : : else
2276 : : {
2277 : 4 : relid = RelationGetRelid(rel);
2278 : 4 : rel_is_index = false;
2279 : : }
2280 : :
2281 : : /*
2282 : : * Do not lock the children until they're processed. Note that we do hold
2283 : : * a lock on the parent partitioned table.
2284 : : */
170 2285 : 20 : inhoids = find_all_inheritors(relid, NoLock, NULL);
2286 [ + - + + : 148 : foreach_oid(child_oid, inhoids)
+ + ]
2287 : : {
2288 : : Oid table_oid,
2289 : : index_oid;
2290 : : RelToCluster *rtc;
2291 : : MemoryContext oldcxt;
2292 : :
2293 [ + + ]: 108 : if (rel_is_index)
2294 : : {
2295 : : /* consider only leaf indexes */
2296 [ + + ]: 80 : if (get_rel_relkind(child_oid) != RELKIND_INDEX)
2297 : 40 : continue;
2298 : :
2299 : : /*
2300 : : * Although we do have a lock on some ancestor partitioned index,
2301 : : * we may not have one on the immediate parent, so this lookup may
2302 : : * still return invalid.
2303 : : */
38 2304 : 40 : table_oid = IndexGetRelation(child_oid, true);
2305 [ - + ]: 40 : if (!OidIsValid(table_oid))
38 alvherre@kurilemu.de 2306 :UBC 0 : continue;
170 alvherre@kurilemu.de 2307 :CBC 40 : index_oid = child_oid;
2308 : : }
2309 : : else
2310 : : {
2311 : : /* consider only leaf relations */
2312 [ + + ]: 28 : if (get_rel_relkind(child_oid) != RELKIND_RELATION)
2313 : 16 : continue;
2314 : :
2315 : 12 : table_oid = child_oid;
2316 : 12 : index_oid = InvalidOid;
2317 : : }
2318 : :
2319 : : /*
2320 : : * It's possible that the user does not have privileges to CLUSTER the
2321 : : * leaf partition despite having them on the partitioned table. Skip
2322 : : * if so.
2323 : : */
38 2324 [ + + ]: 52 : if (!repack_is_permitted_for_relation(stmt->command, table_oid,
2325 : : GetUserId(), false))
1162 nathan@postgresql.or 2326 : 12 : continue;
2327 : :
2328 : : /* Use a permanent memory context for the result list */
170 alvherre@kurilemu.de 2329 : 40 : oldcxt = MemoryContextSwitchTo(permcxt);
260 michael@paquier.xyz 2330 : 40 : rtc = palloc_object(RelToCluster);
170 alvherre@kurilemu.de 2331 : 40 : rtc->tableOid = table_oid;
2332 : 40 : rtc->indexOid = index_oid;
1608 alvherre@alvh.no-ip. 2333 : 40 : rtcs = lappend(rtcs, rtc);
170 alvherre@kurilemu.de 2334 : 40 : MemoryContextSwitchTo(oldcxt);
2335 : : }
2336 : :
2337 : : /* close parent relation, releasing lock on it */
38 2338 : 20 : table_close(rel, AccessExclusiveLock);
2339 : :
1608 alvherre@alvh.no-ip. 2340 : 20 : return rtcs;
2341 : : }
2342 : :
2343 : :
2344 : : /*
2345 : : * Return whether userid has privileges to execute REPACK/CLUSTER on relid.
2346 : : *
2347 : : * The relation may already be locked by caller, in which case it cannot
2348 : : * possibly go missing; otherwise it may have been removed recently. If
2349 : : * it's been removed, silently return false. If the relation exists but
2350 : : * the user doesn't have the required privs, emit a WARNING and return false.
2351 : : *
2352 : : * Otherwise the relation exists and user has required perms, so return true.
2353 : : */
2354 : : static bool
8 alvherre@kurilemu.de 2355 : 3252 : repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid,
2356 : : bool already_locked)
2357 : : {
48 2358 : 3252 : bool is_missing = false;
2359 : : AclResult result;
2360 : : char *relname;
2361 : :
170 2362 [ + + - + ]: 3252 : Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
8 2363 [ + + - + ]: 3252 : Assert(!already_locked ||
2364 : : CheckRelationOidLockedByMe(relid, AccessShareLock, true));
2365 : :
48 2366 : 3252 : result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
2367 : :
2368 : : /*
2369 : : * If the relation was concurrently dropped, nothing to do. This is only
2370 : : * reachable when the caller doesn't already have a lock on the relation.
2371 : : */
2372 [ - + ]: 3252 : if (is_missing)
2373 : : {
8 alvherre@kurilemu.de 2374 [ # # ]:UBC 0 : Assert(!already_locked);
48 2375 : 0 : return false;
2376 : : }
2377 : :
48 alvherre@kurilemu.de 2378 [ + + ]:CBC 3252 : if (result == ACLCHECK_OK)
897 nathan@postgresql.or 2379 : 104 : return true;
2380 : :
2381 : : /*
2382 : : * The relation can also be dropped after we tested its ACL and before we
2383 : : * read its relname, so be careful here.
2384 : : */
48 alvherre@kurilemu.de 2385 : 3148 : relname = get_rel_name(relid);
2386 [ + - ]: 3148 : if (relname != NULL)
2387 : : {
2388 [ + - ]: 3148 : ereport(WARNING,
2389 : : errmsg("permission denied to execute %s on \"%s\", skipping it",
2390 : : RepackCommandAsString(cmd), relname));
2391 : 3148 : pfree(relname);
2392 : : }
2393 : :
897 nathan@postgresql.or 2394 : 3148 : return false;
2395 : : }
2396 : :
2397 : :
2398 : : /*
2399 : : * Given a RepackStmt with an indicated relation name, resolve the relation
2400 : : * name, obtain lock on it, then determine what to do based on the relation
2401 : : * type: if it's table and not partitioned, repack it as indicated (using an
2402 : : * existing clustered index, or following the given one), and return NULL.
2403 : : *
2404 : : * On the other hand, if the table is partitioned, do nothing further and
2405 : : * instead return the opened and locked relcache entry, so that caller can
2406 : : * process the partitions using the multiple-table handling code. In this
2407 : : * case, if an index name is given, it's up to the caller to resolve it.
2408 : : */
2409 : : static Relation
143 alvherre@kurilemu.de 2410 : 216 : process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel,
2411 : : ClusterParams *params)
2412 : : {
2413 : : Relation rel;
2414 : : Oid tableOid;
2415 : :
170 2416 [ - + ]: 216 : Assert(stmt->relation != NULL);
2417 [ + + - + ]: 216 : Assert(stmt->command == REPACK_COMMAND_CLUSTER ||
2418 : : stmt->command == REPACK_COMMAND_REPACK);
2419 : :
2420 : : /*
2421 : : * Make sure ANALYZE is specified if a column list is present.
2422 : : */
2423 [ + + + + ]: 216 : if ((params->options & CLUOPT_ANALYZE) == 0 && stmt->relation->va_cols != NIL)
2424 [ + - ]: 4 : ereport(ERROR,
2425 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2426 : : errmsg("ANALYZE option must be specified when a column list is provided"));
2427 : :
2428 : : /* Find, lock, and check permissions on the table. */
2429 : 212 : tableOid = RangeVarGetRelidExtended(stmt->relation->relation,
2430 : : lockmode,
2431 : : 0,
2432 : : RangeVarCallbackMaintainsTable,
2433 : : NULL);
2434 : 204 : rel = table_open(tableOid, NoLock);
2435 : :
2436 : : /*
2437 : : * Reject clustering a remote temp table ... their local buffer manager is
2438 : : * not going to cope.
2439 : : */
2440 [ + + + + ]: 204 : if (RELATION_IS_OTHER_TEMP(rel))
2441 [ + - ]: 1 : ereport(ERROR,
2442 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2443 : : /*- translator: first %s is name of a SQL command, eg. REPACK */
2444 : : errmsg("cannot execute %s on temporary tables of other sessions",
2445 : : RepackCommandAsString(stmt->command)));
2446 : :
2447 : : /*
2448 : : * For partitioned tables, let caller handle this. Otherwise, process it
2449 : : * here and we're done.
2450 : : */
2451 [ + + ]: 203 : if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
2452 : 33 : return rel;
2453 : : else
2454 : : {
143 2455 : 170 : Oid indexOid = InvalidOid;
2456 : :
170 2457 : 170 : indexOid = determine_clustered_index(rel, stmt->usingindex,
2458 : 170 : stmt->indexname);
2459 [ + + ]: 162 : if (OidIsValid(indexOid))
143 2460 : 139 : check_index_is_clusterable(rel, indexOid, lockmode);
2461 : :
2462 : 150 : cluster_rel(stmt->command, rel, indexOid, params, isTopLevel);
2463 : :
2464 : : /*
2465 : : * Do an analyze, if requested. We close the transaction and start a
2466 : : * new one, so that we don't hold the stronger lock for longer than
2467 : : * needed.
2468 : : */
170 2469 [ + + ]: 139 : if (params->options & CLUOPT_ANALYZE)
2470 : : {
2471 : 8 : VacuumParams vac_params = {0};
2472 : :
2473 : 8 : PopActiveSnapshot();
2474 : 8 : CommitTransactionCommand();
2475 : :
2476 : 8 : StartTransactionCommand();
2477 : 8 : PushActiveSnapshot(GetTransactionSnapshot());
2478 : :
2479 : 8 : vac_params.options |= VACOPT_ANALYZE;
2480 [ - + ]: 8 : if (params->options & CLUOPT_VERBOSE)
170 alvherre@kurilemu.de 2481 :UBC 0 : vac_params.options |= VACOPT_VERBOSE;
149 nathan@postgresql.or 2482 :CBC 8 : analyze_rel(tableOid, NULL, &vac_params,
170 alvherre@kurilemu.de 2483 : 8 : stmt->relation->va_cols, true, NULL);
2484 : 8 : PopActiveSnapshot();
2485 : 8 : CommandCounterIncrement();
2486 : : }
2487 : :
2488 : 139 : return NULL;
2489 : : }
2490 : : }
2491 : :
2492 : : /*
2493 : : * Given a relation and the usingindex/indexname options in a
2494 : : * REPACK USING INDEX or CLUSTER command, return the OID of the
2495 : : * index to use for clustering the table.
2496 : : *
2497 : : * Caller must hold lock on the relation so that the set of indexes
2498 : : * doesn't change, and must call check_index_is_clusterable.
2499 : : */
2500 : : static Oid
2501 : 190 : determine_clustered_index(Relation rel, bool usingindex, const char *indexname)
2502 : : {
2503 : : Oid indexOid;
2504 : :
2505 [ + + + + ]: 190 : if (indexname == NULL && usingindex)
2506 : : {
2507 : : /*
2508 : : * If USING INDEX with no name is given, find a clustered index, or
2509 : : * error out if none.
2510 : : */
2511 : 20 : indexOid = InvalidOid;
2512 [ + - + + : 44 : foreach_oid(idxoid, RelationGetIndexList(rel))
+ + ]
2513 : : {
2514 [ + + ]: 20 : if (get_index_isclustered(idxoid))
2515 : : {
2516 : 16 : indexOid = idxoid;
2517 : 16 : break;
2518 : : }
2519 : : }
2520 : :
2521 [ + + ]: 20 : if (!OidIsValid(indexOid))
2522 [ + - ]: 4 : ereport(ERROR,
2523 : : errcode(ERRCODE_UNDEFINED_OBJECT),
2524 : : errmsg("there is no previously clustered index for table \"%s\"",
2525 : : RelationGetRelationName(rel)));
2526 : : }
2527 [ + + ]: 170 : else if (indexname != NULL)
2528 : : {
2529 : : /* An index was specified; obtain its OID. */
2530 : 147 : indexOid = get_relname_relid(indexname, rel->rd_rel->relnamespace);
2531 [ + + ]: 147 : if (!OidIsValid(indexOid))
2532 [ + - ]: 4 : ereport(ERROR,
2533 : : errcode(ERRCODE_UNDEFINED_OBJECT),
2534 : : errmsg("index \"%s\" for table \"%s\" does not exist",
2535 : : indexname, RelationGetRelationName(rel)));
2536 : : }
2537 : : else
2538 : 23 : indexOid = InvalidOid;
2539 : :
2540 : 182 : return indexOid;
2541 : : }
2542 : :
2543 : : static const char *
2544 : 3622 : RepackCommandAsString(RepackCommand cmd)
2545 : : {
2546 [ + + + - ]: 3622 : switch (cmd)
2547 : : {
2548 : 3211 : case REPACK_COMMAND_REPACK:
2549 : 3211 : return "REPACK";
2550 : 226 : case REPACK_COMMAND_VACUUMFULL:
2551 : 226 : return "VACUUM";
2552 : 185 : case REPACK_COMMAND_CLUSTER:
2553 : 185 : return "CLUSTER";
2554 : : }
170 alvherre@kurilemu.de 2555 :UBC 0 : return "???"; /* keep compiler quiet */
2556 : : }
2557 : :
2558 : : /*
2559 : : * Apply all the changes stored in 'file'.
2560 : : */
2561 : : static void
143 alvherre@kurilemu.de 2562 :CBC 14 : apply_concurrent_changes(BufFile *file, ChangeContext *chgcxt)
2563 : : {
2564 : 14 : ConcurrentChangeKind kind = '\0';
2565 : 14 : Relation rel = chgcxt->cc_rel;
2566 : : TupleTableSlot *spilled_tuple;
2567 : : TupleTableSlot *old_update_tuple;
2568 : : TupleTableSlot *ondisk_tuple;
2569 : 14 : bool have_old_tuple = false;
2570 : : MemoryContext oldcxt;
2571 : :
2572 : 14 : spilled_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
2573 : : &TTSOpsVirtual);
2574 : 14 : ondisk_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
2575 : : table_slot_callbacks(rel));
2576 : 14 : old_update_tuple = MakeSingleTupleTableSlot(RelationGetDescr(rel),
2577 : : &TTSOpsVirtual);
2578 : :
2579 [ + + ]: 14 : oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(chgcxt->cc_estate));
2580 : :
2581 : : while (true)
2582 : 40 : {
2583 : : size_t nread;
2584 : 54 : ConcurrentChangeKind prevkind = kind;
2585 : :
2586 [ - + ]: 54 : CHECK_FOR_INTERRUPTS();
2587 : :
2588 : 54 : nread = BufFileReadMaybeEOF(file, &kind, 1, true);
2589 [ + + ]: 54 : if (nread == 0) /* done with the file? */
2590 : 14 : break;
2591 : :
2592 : : /*
2593 : : * If this is the old tuple for an update, read it into the tuple slot
2594 : : * and go to the next one. The update itself will be executed on the
2595 : : * next iteration, when we receive the NEW tuple.
2596 : : */
2597 [ + + ]: 40 : if (kind == CHANGE_UPDATE_OLD)
2598 : : {
2599 : 8 : restore_tuple(file, rel, old_update_tuple);
2600 : 8 : have_old_tuple = true;
2601 : 8 : continue;
2602 : : }
2603 : :
2604 : : /*
2605 : : * Just before an UPDATE or DELETE, we must update the command
2606 : : * counter, because the change could refer to a tuple that we have
2607 : : * just inserted; and before an INSERT, we have to do this also if the
2608 : : * previous command was either update or delete.
2609 : : *
2610 : : * With this approach we don't spend so many CCIs for long strings of
2611 : : * only INSERTs, which can't affect one another.
2612 : : */
2613 [ + + + + ]: 32 : if (kind == CHANGE_UPDATE_NEW || kind == CHANGE_DELETE ||
2614 [ + - + + : 7 : (kind == CHANGE_INSERT && (prevkind == CHANGE_UPDATE_NEW ||
+ + ]
2615 : : prevkind == CHANGE_DELETE)))
2616 : : {
2617 : 29 : CommandCounterIncrement();
2618 : 29 : UpdateActiveSnapshotCommandId();
2619 : : }
2620 : :
2621 : : /*
2622 : : * Now restore the tuple into the slot and execute the change.
2623 : : */
2624 : 32 : restore_tuple(file, rel, spilled_tuple);
2625 : :
2626 [ + + ]: 32 : if (kind == CHANGE_INSERT)
2627 : : {
2628 : 7 : apply_concurrent_insert(rel, spilled_tuple, chgcxt);
2629 : : }
2630 [ + + ]: 25 : else if (kind == CHANGE_DELETE)
2631 : : {
2632 : : bool found;
2633 : :
2634 : : /* Find the tuple to be deleted */
2635 : 3 : found = find_target_tuple(rel, chgcxt, spilled_tuple, ondisk_tuple);
2636 [ - + ]: 3 : if (!found)
91 alvherre@kurilemu.de 2637 [ # # ]:UBC 0 : elog(ERROR, "could not find target tuple");
143 alvherre@kurilemu.de 2638 :CBC 3 : apply_concurrent_delete(rel, ondisk_tuple);
2639 : : }
2640 [ + - ]: 22 : else if (kind == CHANGE_UPDATE_NEW)
2641 : : {
2642 : : TupleTableSlot *key;
2643 : : bool found;
2644 : :
2645 [ + + ]: 22 : if (have_old_tuple)
2646 : 8 : key = old_update_tuple;
2647 : : else
2648 : 14 : key = spilled_tuple;
2649 : :
2650 : : /* Find the tuple to be updated or deleted. */
2651 : 22 : found = find_target_tuple(rel, chgcxt, key, ondisk_tuple);
2652 [ - + ]: 22 : if (!found)
91 alvherre@kurilemu.de 2653 [ # # ]:UBC 0 : elog(ERROR, "could not find target tuple");
2654 : :
2655 : : /*
2656 : : * If 'tup' contains TOAST pointers, they point to the old
2657 : : * relation's toast. Copy the corresponding TOAST pointers for the
2658 : : * new relation from the existing tuple. (The fact that we
2659 : : * received a TOAST pointer here implies that the attribute hasn't
2660 : : * changed.)
2661 : : */
143 alvherre@kurilemu.de 2662 :CBC 22 : adjust_toast_pointers(rel, spilled_tuple, ondisk_tuple);
2663 : :
2664 : 22 : apply_concurrent_update(rel, spilled_tuple, ondisk_tuple, chgcxt);
2665 : :
2666 : 22 : ExecClearTuple(old_update_tuple);
2667 : 22 : have_old_tuple = false;
2668 : : }
2669 : : else
143 alvherre@kurilemu.de 2670 [ # # ]:UBC 0 : elog(ERROR, "unrecognized kind of change: %d", kind);
2671 : :
143 alvherre@kurilemu.de 2672 [ + - ]:CBC 32 : ResetPerTupleExprContext(chgcxt->cc_estate);
2673 : : }
2674 : :
2675 : : /* Cleanup. */
2676 : 14 : ExecDropSingleTupleTableSlot(spilled_tuple);
2677 : 14 : ExecDropSingleTupleTableSlot(ondisk_tuple);
2678 : 14 : ExecDropSingleTupleTableSlot(old_update_tuple);
2679 : :
2680 : 14 : MemoryContextSwitchTo(oldcxt);
2681 : 14 : }
2682 : :
2683 : : /*
2684 : : * Apply an insert from the spill of concurrent changes to the new copy of the
2685 : : * table.
2686 : : */
2687 : : static void
2688 : 7 : apply_concurrent_insert(Relation rel, TupleTableSlot *slot,
2689 : : ChangeContext *chgcxt)
2690 : : {
2691 : : /* Put the tuple in the table, but make sure it won't be decoded */
2692 : 7 : table_tuple_insert(rel, slot, GetCurrentCommandId(true),
2693 : : TABLE_INSERT_NO_LOGICAL, NULL);
2694 : :
2695 : : /* Update indexes with this new tuple. */
2696 : 7 : ExecInsertIndexTuples(chgcxt->cc_rri,
2697 : : chgcxt->cc_estate,
2698 : : 0,
2699 : : slot,
2700 : : NIL, NULL);
2701 : 7 : pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1);
2702 : 7 : }
2703 : :
2704 : : /*
2705 : : * Apply an update from the spill of concurrent changes to the new copy of the
2706 : : * table.
2707 : : */
2708 : : static void
2709 : 22 : apply_concurrent_update(Relation rel, TupleTableSlot *spilled_tuple,
2710 : : TupleTableSlot *ondisk_tuple,
2711 : : ChangeContext *chgcxt)
2712 : : {
2713 : : LockTupleMode lockmode;
2714 : : TM_FailureData tmfd;
2715 : : TU_UpdateIndexes update_indexes;
2716 : : TM_Result res;
2717 : :
2718 : : /*
2719 : : * Carry out the update, skipping logical decoding for it.
2720 : : */
2721 : 22 : res = table_tuple_update(rel, &(ondisk_tuple->tts_tid), spilled_tuple,
2722 : : GetCurrentCommandId(true),
2723 : : TABLE_UPDATE_NO_LOGICAL,
2724 : : InvalidSnapshot,
2725 : : InvalidSnapshot,
2726 : : false,
2727 : : &tmfd, &lockmode, &update_indexes);
2728 [ - + ]: 22 : if (res != TM_Ok)
143 alvherre@kurilemu.de 2729 [ # # ]:UBC 0 : ereport(ERROR,
2730 : : errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2731 : : errmsg("could not apply concurrent %s on relation \"%s\"",
2732 : : "UPDATE", RelationGetRelationName(rel)));
2733 : :
143 alvherre@kurilemu.de 2734 [ + + ]:CBC 22 : if (update_indexes != TU_None)
2735 : : {
2736 : 8 : uint32 flags = EIIT_IS_UPDATE;
2737 : :
2738 [ - + ]: 8 : if (update_indexes == TU_Summarizing)
143 alvherre@kurilemu.de 2739 :UBC 0 : flags |= EIIT_ONLY_SUMMARIZING;
143 alvherre@kurilemu.de 2740 :CBC 8 : ExecInsertIndexTuples(chgcxt->cc_rri,
2741 : : chgcxt->cc_estate,
2742 : : flags,
2743 : : spilled_tuple,
2744 : : NIL, NULL);
2745 : : }
2746 : :
2747 : 22 : pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1);
2748 : 22 : }
2749 : :
2750 : : static void
2751 : 3 : apply_concurrent_delete(Relation rel, TupleTableSlot *slot)
2752 : : {
2753 : : TM_Result res;
2754 : : TM_FailureData tmfd;
2755 : :
2756 : : /*
2757 : : * Delete tuple from the new heap, skipping logical decoding for it.
2758 : : */
2759 : 3 : res = table_tuple_delete(rel, &(slot->tts_tid),
2760 : : GetCurrentCommandId(true),
2761 : : TABLE_DELETE_NO_LOGICAL,
2762 : : InvalidSnapshot, InvalidSnapshot,
2763 : : false,
2764 : : &tmfd);
2765 : :
2766 [ - + ]: 3 : if (res != TM_Ok)
143 alvherre@kurilemu.de 2767 [ # # ]:UBC 0 : ereport(ERROR,
2768 : : errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
2769 : : errmsg("could not apply concurrent %s on relation \"%s\"",
2770 : : "DELETE", RelationGetRelationName(rel)));
2771 : :
143 alvherre@kurilemu.de 2772 :CBC 3 : pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1);
2773 : 3 : }
2774 : :
2775 : : /*
2776 : : * Read tuple from file and put it in the input slot. All memory is allocated
2777 : : * in the current memory context; caller is responsible for freeing it as
2778 : : * appropriate.
2779 : : *
2780 : : * External attributes are stored in separate memory chunks, in order to avoid
2781 : : * exceeding MaxAllocSize - that could happen if the individual attributes are
2782 : : * smaller than MaxAllocSize but the whole tuple is bigger.
2783 : : */
2784 : : static void
2785 : 40 : restore_tuple(BufFile *file, Relation relation, TupleTableSlot *slot)
2786 : : {
2787 : : uint32 t_len;
2788 : : HeapTuple tup;
2789 : : int natt_ext;
2790 : :
2791 : : /* Read the tuple. */
2792 : 40 : BufFileReadExact(file, &t_len, sizeof(t_len));
2793 : 40 : tup = (HeapTuple) palloc(HEAPTUPLESIZE + t_len);
2794 : 40 : tup->t_data = (HeapTupleHeader) ((char *) tup + HEAPTUPLESIZE);
2795 : 40 : BufFileReadExact(file, tup->t_data, t_len);
2796 : 40 : tup->t_len = t_len;
2797 : 40 : ItemPointerSetInvalid(&tup->t_self);
2798 : 40 : tup->t_tableOid = RelationGetRelid(relation);
2799 : :
2800 : : /*
2801 : : * Put the tuple we read in a slot. This deforms it, so that we can hack
2802 : : * the external attributes in place.
2803 : : */
2804 : 40 : ExecForceStoreHeapTuple(tup, slot, false);
2805 : :
2806 : : /*
2807 : : * Next, read any attributes we stored separately into the tts_values
2808 : : * array elements expecting them, if any. This matches
2809 : : * repack_store_change.
2810 : : */
2811 : 40 : BufFileReadExact(file, &natt_ext, sizeof(natt_ext));
2812 [ + + ]: 40 : if (natt_ext > 0)
2813 : : {
2814 : 11 : TupleDesc desc = slot->tts_tupleDescriptor;
2815 : :
2816 [ + + ]: 66 : for (int i = 0; i < desc->natts; i++)
2817 : : {
2818 : 55 : CompactAttribute *attr = TupleDescCompactAttr(desc, i);
2819 : : varlena *varlen;
2820 : : uint64 chunk_header;
2821 : : void *value;
2822 : : Size varlensz;
2823 : :
2824 [ + + + + ]: 55 : if (attr->attisdropped || attr->attlen != -1)
2825 : 40 : continue;
2826 [ - + ]: 22 : if (slot_attisnull(slot, i + 1))
143 alvherre@kurilemu.de 2827 :UBC 0 : continue;
143 alvherre@kurilemu.de 2828 :CBC 22 : varlen = (varlena *) DatumGetPointer(slot->tts_values[i]);
2829 [ + + ]: 22 : if (!VARATT_IS_EXTERNAL_INDIRECT(varlen))
2830 : 7 : continue;
2831 : 15 : slot_getsomeattrs(slot, i + 1);
2832 : :
2833 : 15 : BufFileReadExact(file, &chunk_header, VARHDRSZ);
2834 : 15 : varlensz = VARSIZE_ANY(&chunk_header);
2835 : :
2836 : 15 : value = palloc(varlensz);
121 2837 : 15 : memcpy(value, &chunk_header, VARHDRSZ);
143 2838 : 15 : BufFileReadExact(file, (char *) value + VARHDRSZ, varlensz - VARHDRSZ);
2839 : :
2840 : 15 : slot->tts_values[i] = PointerGetDatum(value);
2841 : 15 : natt_ext--;
2842 [ - + ]: 15 : if (natt_ext < 0)
72 alvherre@kurilemu.de 2843 [ # # ]:UBC 0 : elog(ERROR, "insufficient number of attributes stored separately");
2844 : : }
2845 : :
72 alvherre@kurilemu.de 2846 [ - + ]:CBC 11 : if (natt_ext != 0)
72 alvherre@kurilemu.de 2847 [ # # ]:UBC 0 : elog(ERROR,
2848 : : "unexpected number of attributes stored separately (%d remaining)",
2849 : : natt_ext);
2850 : : }
143 alvherre@kurilemu.de 2851 :CBC 40 : }
2852 : :
2853 : : /*
2854 : : * Adjust 'dest' replacing any EXTERNAL_ONDISK toast pointers with the
2855 : : * corresponding ones from 'src'.
2856 : : */
2857 : : static void
2858 : 22 : adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *src)
2859 : : {
2860 : 22 : TupleDesc desc = dest->tts_tupleDescriptor;
2861 : :
2862 [ + + ]: 112 : for (int i = 0; i < desc->natts; i++)
2863 : : {
2864 : 90 : CompactAttribute *attr = TupleDescCompactAttr(desc, i);
2865 : : varlena *varlena_dst;
2866 : :
2867 [ + + ]: 90 : if (attr->attisdropped)
2868 : 24 : continue;
2869 [ + + ]: 66 : if (attr->attlen != -1)
2870 : 36 : continue;
2871 [ - + ]: 30 : if (slot_attisnull(dest, i + 1))
143 alvherre@kurilemu.de 2872 :UBC 0 : continue;
2873 : :
143 alvherre@kurilemu.de 2874 :CBC 30 : slot_getsomeattrs(dest, i + 1);
2875 : :
2876 : 30 : varlena_dst = (varlena *) DatumGetPointer(dest->tts_values[i]);
2877 [ + + ]: 30 : if (!VARATT_IS_EXTERNAL_ONDISK(varlena_dst))
2878 : 28 : continue;
2879 : 2 : slot_getsomeattrs(src, i + 1);
2880 : :
2881 : 2 : dest->tts_values[i] = src->tts_values[i];
2882 : : }
2883 : 22 : }
2884 : :
2885 : : /*
2886 : : * Find the tuple to be updated or deleted by the given data change, whose
2887 : : * tuple has already been loaded into locator.
2888 : : *
2889 : : * If the tuple is found, put it in retrieved and return true. If the tuple is
2890 : : * not found, return false.
2891 : : */
2892 : : static bool
2893 : 25 : find_target_tuple(Relation rel, ChangeContext *chgcxt, TupleTableSlot *locator,
2894 : : TupleTableSlot *retrieved)
2895 : : {
2896 : 25 : Form_pg_index idx = chgcxt->cc_ident_index->rd_index;
2897 : : IndexScanDesc scan;
108 2898 : 25 : bool retval = false;
2899 : :
2900 : : /*
2901 : : * Scan key is passed by caller, so it does not have to be constructed
2902 : : * multiple times. Key entries have all fields initialized, except for
2903 : : * sk_argument.
2904 : : *
2905 : : * Use the incoming tuple to finalize the scan key.
2906 : : */
143 2907 [ + + ]: 52 : for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
2908 : : {
2909 : 27 : ScanKey entry = &chgcxt->cc_ident_key[i];
2910 : 27 : AttrNumber attno = idx->indkey.values[i];
2911 : :
2912 : 27 : entry->sk_argument = locator->tts_values[attno - 1];
2913 [ - + ]: 27 : Assert(!locator->tts_isnull[attno - 1]);
2914 : : }
2915 : :
2916 : : /* XXX no instrumentation for now */
2917 : 25 : scan = index_beginscan(rel, chgcxt->cc_ident_index, GetActiveSnapshot(),
2918 : : NULL, chgcxt->cc_ident_key_nentries, 0, 0);
2919 : 25 : index_rescan(scan, chgcxt->cc_ident_key, chgcxt->cc_ident_key_nentries, NULL, 0);
108 2920 [ + - ]: 26 : while (index_getnext_slot(scan, ForwardScanDirection, retrieved))
2921 : : {
2922 : : /* Be wary of temporal constraints */
2923 [ + + + + ]: 26 : if (scan->xs_recheck && !identity_key_equal(chgcxt, locator, retrieved))
2924 : : {
2925 [ - + ]: 1 : CHECK_FOR_INTERRUPTS();
2926 : 1 : continue;
2927 : : }
2928 : :
2929 : 25 : retval = true;
2930 : 25 : break;
2931 : : }
143 2932 : 25 : index_endscan(scan);
2933 : :
2934 : 25 : return retval;
2935 : : }
2936 : :
2937 : : /*
2938 : : * Check whether the candidate tuple matches the locator tuple on all replica
2939 : : * identity key columns, using the same equality operators as the identity
2940 : : * index scan. The locator tuple has already been loaded into cc_ident_key.
2941 : : *
2942 : : * This is needed to filter lossy index matches, such as GiST multirange scans
2943 : : * used for temporal constraints.
2944 : : */
2945 : : static bool
108 2946 : 2 : identity_key_equal(ChangeContext *chgcxt, TupleTableSlot *locator,
2947 : : TupleTableSlot *candidate)
2948 : : {
2949 : 2 : slot_getsomeattrs(locator, chgcxt->cc_last_key_attno);
2950 : 2 : slot_getsomeattrs(candidate, chgcxt->cc_last_key_attno);
2951 : :
2952 [ + + ]: 4 : for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
2953 : : {
2954 : 3 : ScanKey entry = &chgcxt->cc_ident_key[i];
2955 : 3 : AttrNumber attno = chgcxt->cc_ident_index->rd_index->indkey.values[i];
2956 : :
2957 [ - + ]: 3 : Assert(attno > 0);
2958 : :
2959 [ - + ]: 3 : if (locator->tts_isnull[attno - 1] != candidate->tts_isnull[attno - 1])
108 alvherre@kurilemu.de 2960 :UBC 0 : return false;
2961 : :
108 alvherre@kurilemu.de 2962 [ - + ]:CBC 3 : if (locator->tts_isnull[attno - 1])
108 alvherre@kurilemu.de 2963 :UBC 0 : continue;
2964 : :
108 alvherre@kurilemu.de 2965 [ + + ]:CBC 3 : if (!DatumGetBool(FunctionCall2Coll(&entry->sk_func,
2966 : : entry->sk_collation,
2967 : 3 : candidate->tts_values[attno - 1],
2968 : : entry->sk_argument)))
2969 : 1 : return false;
2970 : : }
2971 : :
2972 : 1 : return true;
2973 : : }
2974 : :
2975 : : /*
2976 : : * Decode and apply concurrent changes, up to (and including) the record whose
2977 : : * LSN is 'end_of_wal'.
2978 : : *
2979 : : * XXX the names "process_concurrent_changes" and "apply_concurrent_changes"
2980 : : * are far too similar to each other.
2981 : : */
2982 : : static void
143 2983 : 14 : process_concurrent_changes(XLogRecPtr end_of_wal, ChangeContext *chgcxt, bool done)
2984 : : {
2985 : : DecodingWorkerShared *shared;
2986 : : char fname[MAXPGPATH];
2987 : : BufFile *file;
2988 : :
2989 : 14 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
2990 : : PROGRESS_REPACK_PHASE_CATCH_UP);
2991 : :
2992 : : /* Ask the worker for the file. */
2993 : 14 : shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
2994 : 14 : SpinLockAcquire(&shared->mutex);
2995 : 14 : shared->lsn_upto = end_of_wal;
2996 : 14 : shared->done = done;
2997 : 14 : SpinLockRelease(&shared->mutex);
2998 : :
2999 : : /*
3000 : : * The worker needs to finish processing of the current WAL record. Even
3001 : : * if it's idle, it'll need to close the output file. Thus we're likely to
3002 : : * wait, so prepare for sleep.
3003 : : */
3004 : 14 : ConditionVariablePrepareToSleep(&shared->cv);
3005 : : for (;;)
3006 : 14 : {
3007 : : int last_exported;
3008 : :
3009 : 28 : SpinLockAcquire(&shared->mutex);
3010 : 28 : last_exported = shared->last_exported;
3011 : 28 : SpinLockRelease(&shared->mutex);
3012 : :
3013 : : /*
3014 : : * Has the worker exported the file we are waiting for?
3015 : : */
3016 [ + + ]: 28 : if (last_exported == chgcxt->cc_file_seq)
3017 : 14 : break;
3018 : :
3019 : 14 : ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
3020 : : }
3021 : 14 : ConditionVariableCancelSleep();
3022 : :
3023 : : /* Open the file. */
3024 : 14 : DecodingWorkerFileName(fname, shared->relid, chgcxt->cc_file_seq);
3025 : 14 : file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
3026 : 14 : apply_concurrent_changes(file, chgcxt);
3027 : :
3028 : 14 : BufFileClose(file);
3029 : :
3030 : : /* Get ready for the next file. */
3031 : 14 : chgcxt->cc_file_seq++;
3032 : 14 : }
3033 : :
3034 : : /*
3035 : : * Initialize the ChangeContext struct for the given relation, with
3036 : : * the given index as identity index.
3037 : : */
3038 : : static void
3039 : 7 : initialize_change_context(ChangeContext *chgcxt,
3040 : : Relation relation, Oid ident_index_id)
3041 : : {
3042 : 7 : chgcxt->cc_rel = relation;
3043 : :
3044 : : /* Only initialize fields needed by ExecInsertIndexTuples(). */
3045 : 7 : chgcxt->cc_estate = CreateExecutorState();
3046 : :
3047 : : /*
3048 : : * Set up a range table for the executor, containing our repacked table as
3049 : : * its only member.
3050 : : */
3051 : : {
3052 : : RangeTblEntry *rte;
55 3053 : 7 : TupleDesc desc = RelationGetDescr(relation);
3054 : 7 : List *perminfos = NIL;
3055 : 7 : Bitmapset *updatedCols = NULL;
3056 : : RTEPermissionInfo *perminfo;
3057 : :
3058 : : /*
3059 : : * For our use, the RTE only needs to have perminfoindex initialized,
3060 : : * but there's no reason to not set the fields whose values we have at
3061 : : * hand.
3062 : : */
3063 : 7 : rte = makeNode(RangeTblEntry);
3064 : 7 : rte->rtekind = RTE_RELATION;
3065 : 7 : rte->relid = RelationGetRelid(relation);
3066 : 7 : rte->relkind = RelationGetForm(relation)->relkind;
3067 : : /* Create the RTEPermissionInfo instance (and set ->perminfoindex). */
3068 : 7 : addRTEPermissionInfo(&perminfos, rte);
3069 : :
3070 : : /*
3071 : : * Initialize updatedCols to show that all columns are updated. This
3072 : : * is of course not necessarily true, and we cannot know this early;
3073 : : * but this is only used by ExecInsertIndexTuples to flag index
3074 : : * updates with no logical value changes, so if it's wrong, nothing
3075 : : * terribly bad happens. We may want to improve this someday though.
3076 : : *
3077 : : * Don't claim that dropped columns are changed though.
3078 : : */
3079 [ + + ]: 25 : for (int i = 0; i < desc->natts; i++)
3080 : : {
3081 : 18 : CompactAttribute *attr = TupleDescCompactAttr(desc, i);
3082 : :
3083 [ + + ]: 18 : if (attr->attisdropped)
3084 : 2 : continue;
3085 : 16 : updatedCols = bms_add_member(updatedCols,
3086 : : i + 1 - FirstLowInvalidHeapAttributeNumber);
3087 : : }
3088 : :
3089 : : /* install updatedCols in the right place */
3090 : 7 : perminfo = getRTEPermissionInfo(perminfos, rte);
3091 : 7 : perminfo->updatedCols = updatedCols;
3092 : :
3093 : : /* finally we can initialize the range table proper */
3094 : 7 : ExecInitRangeTable(chgcxt->cc_estate, list_make1(rte), perminfos,
3095 : : bms_make_singleton(1));
3096 : : }
3097 : :
3098 : : /* Set up our ResultRelInfo to use for index updates */
3099 : 7 : chgcxt->cc_rri = makeNode(ResultRelInfo);
3100 : 7 : InitResultRelInfo(chgcxt->cc_rri, relation, 1, NULL, 0);
143 3101 : 7 : ExecOpenIndices(chgcxt->cc_rri, false);
3102 : :
3103 : : /*
3104 : : * The table's relcache entry already has the relcache entry for the
3105 : : * identity index; find that.
3106 : : */
3107 : 7 : chgcxt->cc_ident_index = NULL;
3108 [ + - ]: 7 : for (int i = 0; i < chgcxt->cc_rri->ri_NumIndices; i++)
3109 : : {
3110 : : Relation ind_rel;
3111 : :
3112 : 7 : ind_rel = chgcxt->cc_rri->ri_IndexRelationDescs[i];
3113 [ + - ]: 7 : if (ind_rel->rd_id == ident_index_id)
3114 : : {
3115 : 7 : chgcxt->cc_ident_index = ind_rel;
3116 : 7 : break;
3117 : : }
3118 : : }
3119 [ - + ]: 7 : if (chgcxt->cc_ident_index == NULL)
91 alvherre@kurilemu.de 3120 [ # # ]:UBC 0 : elog(ERROR, "could not find identity index");
3121 : :
3122 : : /* Set up for scanning said identity index */
3123 : : {
3124 : : Form_pg_index indexForm;
3125 : :
143 alvherre@kurilemu.de 3126 :CBC 7 : indexForm = chgcxt->cc_ident_index->rd_index;
3127 : 7 : chgcxt->cc_ident_key_nentries = indexForm->indnkeyatts;
3128 : 7 : chgcxt->cc_ident_key = (ScanKey) palloc_array(ScanKeyData, indexForm->indnkeyatts);
3129 [ + + ]: 16 : for (int i = 0; i < indexForm->indnkeyatts; i++)
3130 : : {
3131 : : ScanKey entry;
3132 : : Oid opfamily,
3133 : : opcintype,
3134 : : opno,
3135 : : opcode;
3136 : : StrategyNumber eq_strategy;
3137 : :
3138 : 9 : entry = &chgcxt->cc_ident_key[i];
3139 : :
3140 : 9 : opfamily = chgcxt->cc_ident_index->rd_opfamily[i];
3141 : 9 : opcintype = chgcxt->cc_ident_index->rd_opcintype[i];
108 3142 : 9 : eq_strategy = IndexAmTranslateCompareType(COMPARE_EQ,
3143 : 9 : chgcxt->cc_ident_index->rd_rel->relam,
3144 : : opfamily, false);
3145 [ - + ]: 9 : if (eq_strategy == InvalidStrategy)
108 alvherre@kurilemu.de 3146 [ # # ]:UBC 0 : elog(ERROR, "could not find equality strategy for index operator family %u for type %u",
3147 : : opfamily, opcintype);
143 alvherre@kurilemu.de 3148 :CBC 9 : opno = get_opfamily_member(opfamily, opcintype, opcintype,
3149 : : eq_strategy);
3150 [ - + ]: 9 : if (!OidIsValid(opno))
108 alvherre@kurilemu.de 3151 [ # # ]:UBC 0 : elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
3152 : : eq_strategy, opcintype, opcintype, opfamily);
143 alvherre@kurilemu.de 3153 :CBC 9 : opcode = get_opcode(opno);
3154 [ - + ]: 9 : if (!OidIsValid(opcode))
108 alvherre@kurilemu.de 3155 [ # # ]:UBC 0 : elog(ERROR, "missing oprcode for operator %u", opno);
3156 : :
3157 : : /* Initialize everything but argument. */
143 alvherre@kurilemu.de 3158 :CBC 9 : ScanKeyInit(entry,
3159 : 9 : i + 1,
3160 : : eq_strategy, opcode,
3161 : : (Datum) 0);
3162 : 9 : entry->sk_collation = chgcxt->cc_ident_index->rd_indcollation[i];
3163 : : }
3164 : : }
3165 : :
3166 : : /* Determine the last column we must deform to read the identity */
108 3167 : 7 : chgcxt->cc_last_key_attno = InvalidAttrNumber;
3168 [ + + ]: 16 : for (int i = 0; i < chgcxt->cc_ident_key_nentries; i++)
3169 : : {
3170 : 9 : AttrNumber attno = chgcxt->cc_ident_index->rd_index->indkey.values[i];
3171 : :
3172 [ - + ]: 9 : Assert(attno > 0);
3173 : 9 : chgcxt->cc_last_key_attno = Max(chgcxt->cc_last_key_attno, attno);
3174 : : }
3175 : :
143 3176 : 7 : chgcxt->cc_file_seq = WORKER_FILE_SNAPSHOT + 1;
3177 : 7 : }
3178 : :
3179 : : /*
3180 : : * Free up resources taken by a ChangeContext.
3181 : : */
3182 : : static void
3183 : 7 : release_change_context(ChangeContext *chgcxt)
3184 : : {
3185 : 7 : ExecCloseIndices(chgcxt->cc_rri);
3186 : 7 : FreeExecutorState(chgcxt->cc_estate);
3187 : : /* XXX are these pfrees necessary? */
3188 : 7 : pfree(chgcxt->cc_rri);
3189 : 7 : pfree(chgcxt->cc_ident_key);
3190 : 7 : }
3191 : :
3192 : : /*
3193 : : * The final steps of rebuild_relation() for concurrent processing.
3194 : : *
3195 : : * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its
3196 : : * clustering index (if one is passed) are still locked in a mode that allows
3197 : : * concurrent data changes. On exit, both tables and their indexes are closed,
3198 : : * but locked in AccessExclusiveLock mode.
3199 : : */
3200 : : static void
3201 : 7 : rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap,
3202 : : Oid identIdx, TransactionId frozenXid,
3203 : : MultiXactId cutoffMulti)
3204 : : {
3205 : : List *ind_oids_new;
3206 : 7 : Oid old_table_oid = RelationGetRelid(OldHeap);
3207 : 7 : Oid new_table_oid = RelationGetRelid(NewHeap);
3208 : 7 : List *ind_oids_old = RelationGetIndexList(OldHeap);
3209 : : ListCell *lc,
3210 : : *lc2;
3211 : : char relpersistence;
3212 : : bool is_system_catalog;
3213 : : Oid ident_idx_new;
3214 : : XLogRecPtr end_of_wal;
3215 : : List *indexrels;
3216 : : ChangeContext chgcxt;
3217 : :
3218 [ - + ]: 7 : Assert(CheckRelationLockedByMe(OldHeap, ShareUpdateExclusiveLock, false));
3219 [ - + ]: 7 : Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false));
3220 : :
3221 : : /*
3222 : : * Unlike the exclusive case, we build new indexes for the new relation
3223 : : * rather than swapping the storage and reindexing the old relation. The
3224 : : * point is that the index build can take some time, so we do it before we
3225 : : * get AccessExclusiveLock on the old heap and therefore we cannot swap
3226 : : * the heap storage yet.
3227 : : *
3228 : : * index_create() will lock the new indexes using AccessExclusiveLock - no
3229 : : * need to change that. At the same time, we use ShareUpdateExclusiveLock
3230 : : * to lock the existing indexes - that should be enough to prevent others
3231 : : * from changing them while we're repacking the relation. The lock on
3232 : : * table should prevent others from changing the index column list, but
3233 : : * might not be enough for commands like ALTER INDEX ... SET ... (Those
3234 : : * are not necessarily dangerous, but can make user confused if the
3235 : : * changes they do get lost due to REPACK.)
3236 : : */
3237 : 7 : ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old);
3238 : :
3239 : : /*
3240 : : * The identity index in the new relation appears in the same relative
3241 : : * position as the corresponding index in the old relation. Find it.
3242 : : */
3243 : 7 : ident_idx_new = InvalidOid;
3244 [ + - + - : 14 : foreach_oid(ind_old, ind_oids_old)
+ + ]
3245 : : {
3246 [ + - ]: 7 : if (identIdx == ind_old)
3247 : : {
3248 : 7 : int pos = foreach_current_index(ind_old);
3249 : :
115 3250 [ - + ]: 7 : if (list_length(ind_oids_new) <= pos)
143 alvherre@kurilemu.de 3251 [ # # ]:UBC 0 : elog(ERROR, "list of new indexes too short");
143 alvherre@kurilemu.de 3252 :CBC 7 : ident_idx_new = list_nth_oid(ind_oids_new, pos);
3253 : 7 : break;
3254 : : }
3255 : : }
3256 [ - + ]: 7 : if (!OidIsValid(ident_idx_new))
143 alvherre@kurilemu.de 3257 [ # # ]:UBC 0 : elog(ERROR, "could not find index matching \"%s\" at the new relation",
3258 : : get_rel_name(identIdx));
3259 : :
3260 : : /* Gather information to apply concurrent changes. */
143 alvherre@kurilemu.de 3261 :CBC 7 : initialize_change_context(&chgcxt, NewHeap, ident_idx_new);
3262 : :
3263 : : /*
3264 : : * During testing, wait for another backend to perform concurrent data
3265 : : * changes which we will process below.
3266 : : */
3267 : 7 : INJECTION_POINT("repack-concurrently-before-lock", NULL);
3268 : :
3269 : : /*
3270 : : * Flush all WAL records inserted so far (possibly except for the last
3271 : : * incomplete page; see GetInsertRecPtr), to minimize the amount of data
3272 : : * we need to flush while holding exclusive lock on the source table.
3273 : : */
3274 : 7 : XLogFlush(GetXLogInsertEndRecPtr());
3275 : 7 : end_of_wal = GetFlushRecPtr(NULL);
3276 : :
3277 : : /*
3278 : : * Apply concurrent changes first time, to minimize the time we need to
3279 : : * hold AccessExclusiveLock. (Quite some amount of WAL could have been
3280 : : * written during the data copying and index creation.)
3281 : : */
3282 : 7 : process_concurrent_changes(end_of_wal, &chgcxt, false);
3283 : :
3284 : : /*
3285 : : * Acquire AccessExclusiveLock on the table, its TOAST relation (if there
3286 : : * is one), all its indexes, so that we can swap the files.
3287 : : */
3288 : 7 : LockRelationOid(old_table_oid, AccessExclusiveLock);
3289 : :
3290 : : /*
3291 : : * Lock all indexes now, not only the clustering one: all indexes need to
3292 : : * have their files swapped. While doing that, store their relation
3293 : : * references in a zero-terminated array, to handle predicate locks below.
3294 : : */
3295 : 7 : indexrels = NIL;
3296 [ + - + + : 22 : foreach_oid(ind_oid, ind_oids_old)
+ + ]
3297 : : {
3298 : : Relation index;
3299 : :
3300 : 8 : index = index_open(ind_oid, AccessExclusiveLock);
3301 : :
3302 : : /*
3303 : : * Some things about the index may have changed before we locked the
3304 : : * index, such as ALTER INDEX RENAME. We don't need to do anything
3305 : : * here to absorb those changes in the new index.
3306 : : */
3307 : 8 : indexrels = lappend(indexrels, index);
3308 : : }
3309 : :
3310 : : /*
3311 : : * Lock the OldHeap's TOAST relation exclusively - again, the lock is
3312 : : * needed to swap the files.
3313 : : */
3314 [ + + ]: 7 : if (OidIsValid(OldHeap->rd_rel->reltoastrelid))
3315 : 3 : LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock);
3316 : :
3317 : : /*
3318 : : * Tuples and pages of the old heap will be gone, but the heap will stay.
3319 : : */
3320 : 7 : TransferPredicateLocksToHeapRelation(OldHeap);
3321 [ + - + + : 22 : foreach_ptr(RelationData, index, indexrels)
+ + ]
3322 : : {
3323 : 8 : TransferPredicateLocksToHeapRelation(index);
3324 : 8 : index_close(index, NoLock);
3325 : : }
3326 : 7 : list_free(indexrels);
3327 : :
3328 : : /*
3329 : : * Flush WAL again, to make sure that all changes committed while we were
3330 : : * waiting for the exclusive lock are available for decoding.
3331 : : */
3332 : 7 : XLogFlush(GetXLogInsertEndRecPtr());
3333 : 7 : end_of_wal = GetFlushRecPtr(NULL);
3334 : :
3335 : : /*
3336 : : * Apply the concurrent changes again. Indicate that the decoding worker
3337 : : * won't be needed anymore.
3338 : : */
3339 : 7 : process_concurrent_changes(end_of_wal, &chgcxt, true);
3340 : :
3341 : : /* Remember info about rel before closing OldHeap */
3342 : 7 : relpersistence = OldHeap->rd_rel->relpersistence;
3343 : 7 : is_system_catalog = IsSystemRelation(OldHeap);
3344 : :
3345 : 7 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
3346 : : PROGRESS_REPACK_PHASE_SWAP_REL_FILES);
3347 : :
3348 : : /*
3349 : : * Even ShareUpdateExclusiveLock should have prevented others from
3350 : : * creating / dropping indexes (even using the CONCURRENTLY option), so we
3351 : : * do not need to check whether the lists match.
3352 : : */
3353 [ + - + + : 15 : forboth(lc, ind_oids_old, lc2, ind_oids_new)
+ - + + +
+ + - +
+ ]
3354 : : {
3355 : 8 : Oid ind_old = lfirst_oid(lc);
3356 : 8 : Oid ind_new = lfirst_oid(lc2);
3357 : 8 : Oid mapped_tables[4] = {0};
3358 : :
3359 : 8 : swap_relation_files(ind_old, ind_new,
3360 : : (old_table_oid == RelationRelationId),
3361 : : false, /* swap_toast_by_content */
3362 : : true,
3363 : : InvalidTransactionId,
3364 : : InvalidMultiXactId,
3365 : : mapped_tables);
3366 : :
3367 : : #ifdef USE_ASSERT_CHECKING
3368 : :
3369 : : /*
3370 : : * Concurrent processing is not supported for system relations, so
3371 : : * there should be no mapped tables.
3372 : : */
3373 [ + + ]: 40 : for (int i = 0; i < 4; i++)
3374 [ - + ]: 32 : Assert(!OidIsValid(mapped_tables[i]));
3375 : : #endif
3376 : : }
3377 : :
3378 : : /* The new indexes must be visible for deletion. */
3379 : 7 : CommandCounterIncrement();
3380 : :
3381 : : /* Close the old heap but keep lock until transaction commit. */
3382 : 7 : table_close(OldHeap, NoLock);
3383 : : /* Close the new heap. (We didn't have to open its indexes). */
3384 : 7 : table_close(NewHeap, NoLock);
3385 : :
3386 : : /* Cleanup what we don't need anymore. (And close the identity index.) */
3387 : 7 : release_change_context(&chgcxt);
3388 : :
3389 : : /*
3390 : : * Swap the relations and their TOAST relations and TOAST indexes. This
3391 : : * also drops the new relation and its indexes.
3392 : : *
3393 : : * (System catalogs are currently not supported.)
3394 : : */
3395 [ - + ]: 7 : Assert(!is_system_catalog);
3396 : 7 : finish_heap_swap(old_table_oid, new_table_oid,
3397 : : is_system_catalog,
3398 : : false, /* swap_toast_by_content */
3399 : : false,
3400 : : true,
3401 : : false, /* reindex */
3402 : : frozenXid, cutoffMulti,
3403 : : relpersistence);
3404 : 7 : }
3405 : :
3406 : : /*
3407 : : * Build indexes on NewHeap according to those on OldHeap.
3408 : : *
3409 : : * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end
3410 : : * up locked using ShareUpdateExclusiveLock.
3411 : : *
3412 : : * A list of OIDs of the corresponding indexes created on NewHeap is
3413 : : * returned. The order of items does match, so we can use these arrays to swap
3414 : : * index storage.
3415 : : */
3416 : : static List *
3417 : 7 : build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes)
3418 : : {
3419 : 7 : List *result = NIL;
3420 : :
3421 : 7 : pgstat_progress_update_param(PROGRESS_REPACK_PHASE,
3422 : : PROGRESS_REPACK_PHASE_REBUILD_INDEX);
3423 : :
3424 [ + - + + : 22 : foreach_oid(oldindex, OldIndexes)
+ + ]
3425 : : {
3426 : : Oid newindex;
3427 : : char *newName;
3428 : : Relation ind;
3429 : :
3430 : 8 : ind = index_open(oldindex, ShareUpdateExclusiveLock);
3431 : :
3432 : 8 : newName = ChooseRelationName(get_rel_name(oldindex),
3433 : : NULL,
3434 : : "repacknew",
3435 : 8 : get_rel_namespace(ind->rd_index->indrelid),
3436 : : false);
3437 : 8 : newindex = index_create_copy(NewHeap, INDEX_CREATE_SUPPRESS_PROGRESS,
3438 : 8 : oldindex, ind->rd_rel->reltablespace,
3439 : : newName);
3440 : 8 : copy_index_constraints(ind, newindex, RelationGetRelid(NewHeap));
3441 : 8 : result = lappend_oid(result, newindex);
3442 : :
3443 : 8 : index_close(ind, NoLock);
3444 : : }
3445 : :
3446 : 7 : return result;
3447 : : }
3448 : :
3449 : : /*
3450 : : * Create a transient copy of a constraint -- supported by a transient
3451 : : * copy of the index that supports the original constraint.
3452 : : *
3453 : : * When repacking a table that contains exclusion constraints, the executor
3454 : : * relies on these constraints being properly catalogued. These copies are
3455 : : * to support that.
3456 : : *
3457 : : * We don't need the constraints for anything else (the original constraints
3458 : : * will be there once repack completes), so we add pg_depend entries so that
3459 : : * they are dropped when the transient table is dropped.
3460 : : */
3461 : : static void
3462 : 8 : copy_index_constraints(Relation old_index, Oid new_index_id, Oid new_heap_id)
3463 : : {
3464 : : ScanKeyData skey;
3465 : : Relation rel;
3466 : : TupleDesc desc;
3467 : : SysScanDesc scan;
3468 : : HeapTuple tup;
3469 : : ObjectAddress objrel;
3470 : :
3471 : 8 : rel = table_open(ConstraintRelationId, RowExclusiveLock);
3472 : 8 : ObjectAddressSet(objrel, RelationRelationId, new_heap_id);
3473 : :
3474 : : /*
3475 : : * Retrieve the constraints supported by the old index and create an
3476 : : * identical one that points to the new index.
3477 : : */
3478 : 8 : ScanKeyInit(&skey,
3479 : : Anum_pg_constraint_conrelid,
3480 : : BTEqualStrategyNumber, F_OIDEQ,
3481 : 8 : ObjectIdGetDatum(old_index->rd_index->indrelid));
3482 : 8 : scan = systable_beginscan(rel, ConstraintRelidTypidNameIndexId, true,
3483 : : NULL, 1, &skey);
3484 : 8 : desc = RelationGetDescr(rel);
3485 [ + + ]: 26 : while (HeapTupleIsValid(tup = systable_getnext(scan)))
3486 : : {
3487 : 18 : Form_pg_constraint conform = (Form_pg_constraint) GETSTRUCT(tup);
3488 : : Oid oid;
3489 : 18 : Datum values[Natts_pg_constraint] = {0};
3490 : 18 : bool nulls[Natts_pg_constraint] = {0};
3491 : 18 : bool replaces[Natts_pg_constraint] = {0};
3492 : : HeapTuple new_tup;
3493 : : ObjectAddress objcon;
3494 : :
3495 [ + + ]: 18 : if (conform->conindid != RelationGetRelid(old_index))
3496 : 11 : continue;
3497 : :
3498 : 7 : oid = GetNewOidWithIndex(rel, ConstraintOidIndexId,
3499 : : Anum_pg_constraint_oid);
3500 : 7 : values[Anum_pg_constraint_oid - 1] = ObjectIdGetDatum(oid);
3501 : 7 : replaces[Anum_pg_constraint_oid - 1] = true;
3502 : 7 : values[Anum_pg_constraint_conrelid - 1] = ObjectIdGetDatum(new_heap_id);
3503 : 7 : replaces[Anum_pg_constraint_conrelid - 1] = true;
3504 : 7 : values[Anum_pg_constraint_conindid - 1] = ObjectIdGetDatum(new_index_id);
3505 : 7 : replaces[Anum_pg_constraint_conindid - 1] = true;
3506 : :
3507 : 7 : new_tup = heap_modify_tuple(tup, desc, values, nulls, replaces);
3508 : :
3509 : : /* Insert it into the catalog. */
3510 : 7 : CatalogTupleInsert(rel, new_tup);
3511 : :
3512 : : /* Create a dependency so it's removed when we drop the new heap. */
3513 : 7 : ObjectAddressSet(objcon, ConstraintRelationId, oid);
3514 : 7 : recordDependencyOn(&objcon, &objrel, DEPENDENCY_AUTO);
3515 : : }
3516 : 8 : systable_endscan(scan);
3517 : :
3518 : 8 : table_close(rel, RowExclusiveLock);
3519 : :
3520 : 8 : CommandCounterIncrement();
3521 : 8 : }
3522 : :
3523 : : /*
3524 : : * Create a transient copy of attribute defaults.
3525 : : *
3526 : : * When repacking a table that has stored generated columns, the executor
3527 : : * relies on these entries to generate the values for them during apply of
3528 : : * concurrent operations. These copies are there to support that.
3529 : : *
3530 : : * We don't need the defaults for anything else, so we add pg_depend entries
3531 : : * so that they are dropped when the transient table is dropped.
3532 : : */
3533 : : static void
55 3534 : 7 : copy_attribute_defaults(Oid old_heap_oid, Oid new_heap_oid)
3535 : : {
3536 : : ScanKeyData skey;
3537 : : Relation rel;
3538 : : Relation att_rel;
3539 : : SysScanDesc scan;
3540 : : HeapTuple def_tup;
3541 : : ObjectAddress objrel;
3542 : :
3543 : 7 : rel = table_open(AttrDefaultRelationId, RowExclusiveLock);
3544 : 7 : att_rel = table_open(AttributeRelationId, RowExclusiveLock);
3545 : :
3546 : 7 : ObjectAddressSet(objrel, RelationRelationId, new_heap_oid);
3547 : :
3548 : 7 : ScanKeyInit(&skey,
3549 : : Anum_pg_attrdef_adrelid,
3550 : : BTEqualStrategyNumber, F_OIDEQ,
3551 : : ObjectIdGetDatum(old_heap_oid));
3552 : 7 : scan = systable_beginscan(rel, AttrDefaultIndexId, true,
3553 : : NULL, 1, &skey);
3554 [ + + ]: 9 : while (HeapTupleIsValid(def_tup = systable_getnext(scan)))
3555 : : {
3556 : : Form_pg_attrdef adform;
3557 : : Oid oid;
3558 : : Datum def_values[Natts_pg_attrdef];
3559 : : bool def_nulls[Natts_pg_attrdef];
3560 : 2 : bool def_replaces[Natts_pg_attrdef] = {0};
3561 : : Datum att_values[Natts_pg_attribute];
3562 : : bool att_nulls[Natts_pg_attribute];
3563 : 2 : bool att_replaces[Natts_pg_attribute] = {0};
3564 : : HeapTuple new_def_tup,
3565 : : att_tup,
3566 : : new_att_tup;
3567 : : ObjectAddress objad;
3568 : :
3569 : 2 : adform = (Form_pg_attrdef) GETSTRUCT(def_tup);
3570 [ - + ]: 2 : Assert(adform->adrelid == old_heap_oid);
3571 : :
3572 : : /*
3573 : : * Insert a new tuple that's identical to the existing one, other than
3574 : : * its OID and the relation it refers to.
3575 : : */
3576 : 2 : oid = GetNewOidWithIndex(rel, AttrDefaultOidIndexId,
3577 : : Anum_pg_attrdef_oid);
3578 : 2 : def_values[Anum_pg_attrdef_oid - 1] = ObjectIdGetDatum(oid);
3579 : 2 : def_nulls[Anum_pg_attrdef_oid - 1] = false;
3580 : 2 : def_replaces[Anum_pg_attrdef_oid - 1] = true;
3581 : 2 : def_values[Anum_pg_attrdef_adrelid - 1] = ObjectIdGetDatum(new_heap_oid);
3582 : 2 : def_nulls[Anum_pg_attrdef_adrelid - 1] = false;
3583 : 2 : def_replaces[Anum_pg_attrdef_adrelid - 1] = true;
3584 : 2 : new_def_tup = heap_modify_tuple(def_tup, RelationGetDescr(rel),
3585 : : def_values, def_nulls, def_replaces);
3586 : 2 : CatalogTupleInsert(rel, new_def_tup);
3587 : :
3588 : : /* Set atthasdef for this attribute in the transient table */
3589 : 2 : att_tup = SearchSysCache2(ATTNUM,
3590 : : ObjectIdGetDatum(new_heap_oid),
3591 : 2 : ObjectIdGetDatum(adform->adnum));
3592 [ - + ]: 2 : if (!HeapTupleIsValid(att_tup))
55 alvherre@kurilemu.de 3593 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for attribute %d of relation %u",
3594 : : adform->adnum, new_heap_oid);
55 alvherre@kurilemu.de 3595 :CBC 2 : att_values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(true);
3596 : 2 : att_nulls[Anum_pg_attribute_atthasdef - 1] = false;
3597 : 2 : att_replaces[Anum_pg_attribute_atthasdef - 1] = true;
3598 : 2 : new_att_tup = heap_modify_tuple(att_tup, RelationGetDescr(att_rel),
3599 : : att_values, att_nulls, att_replaces);
3600 : 2 : CatalogTupleUpdate(att_rel, &new_att_tup->t_self, new_att_tup);
3601 : 2 : ReleaseSysCache(att_tup);
3602 : :
3603 : : /* Add a pg_depend record so it's removed with the transient table */
3604 : 2 : ObjectAddressSet(objad, AttrDefaultRelationId, oid);
3605 : 2 : recordDependencyOn(&objad, &objrel, DEPENDENCY_AUTO);
3606 : : }
3607 : 7 : systable_endscan(scan);
3608 : :
3609 : 7 : table_close(rel, RowExclusiveLock);
3610 : 7 : table_close(att_rel, RowExclusiveLock);
3611 : :
3612 : 7 : CommandCounterIncrement();
3613 : 7 : }
3614 : :
3615 : : /*
3616 : : * Try to start a background worker to perform logical decoding of data
3617 : : * changes applied to relation while REPACK CONCURRENTLY is copying its
3618 : : * contents to a new table.
3619 : : */
3620 : : static void
143 3621 : 7 : start_repack_decoding_worker(Oid relid)
3622 : : {
3623 : : Size size;
3624 : : DecodingWorkerShared *shared;
3625 : : shm_mq *mq;
3626 : : BackgroundWorker bgw;
3627 : :
93 3628 : 7 : decoding_worker = palloc0_object(DecodingWorker);
3629 : :
3630 : : /* Setup shared memory. */
143 3631 : 7 : size = BUFFERALIGN(offsetof(DecodingWorkerShared, error_queue)) +
3632 : : BUFFERALIGN(REPACK_ERROR_QUEUE_SIZE);
93 3633 : 7 : decoding_worker->seg = dsm_create(size, 0);
3634 : :
3635 : 7 : shared = (DecodingWorkerShared *) dsm_segment_address(decoding_worker->seg);
133 3636 : 7 : shared->initialized = false;
143 3637 : 7 : shared->lsn_upto = InvalidXLogRecPtr;
3638 : 7 : shared->done = false;
93 3639 : 7 : SharedFileSetInit(&shared->sfs, decoding_worker->seg);
143 3640 : 7 : shared->last_exported = -1;
3641 : 7 : SpinLockInit(&shared->mutex);
3642 : 7 : shared->dbid = MyDatabaseId;
3643 : :
3644 : : /*
3645 : : * This is the UserId set in cluster_rel(). Security context shouldn't be
3646 : : * needed for decoding worker.
3647 : : */
3648 : 7 : shared->roleid = GetUserId();
3649 : 7 : shared->relid = relid;
3650 : 7 : ConditionVariableInit(&shared->cv);
3651 : 7 : shared->backend_proc = MyProc;
3652 : 7 : shared->backend_pid = MyProcPid;
3653 : 7 : shared->backend_proc_number = MyProcNumber;
3654 : :
3655 : 7 : mq = shm_mq_create((char *) BUFFERALIGN(shared->error_queue),
3656 : : REPACK_ERROR_QUEUE_SIZE);
3657 : 7 : shm_mq_set_receiver(mq, MyProc);
3658 : :
93 3659 : 7 : decoding_worker->error_mqh = shm_mq_attach(mq, decoding_worker->seg, NULL);
3660 : :
143 3661 : 7 : memset(&bgw, 0, sizeof(bgw));
3662 : 7 : snprintf(bgw.bgw_name, BGW_MAXLEN,
3663 : : "REPACK decoding worker for relation \"%s\"",
3664 : : get_rel_name(relid));
3665 : 7 : snprintf(bgw.bgw_type, BGW_MAXLEN, "REPACK decoding worker");
3666 : 7 : bgw.bgw_flags = BGWORKER_SHMEM_ACCESS |
3667 : : BGWORKER_BACKEND_DATABASE_CONNECTION;
3668 : 7 : bgw.bgw_start_time = BgWorkerStart_RecoveryFinished;
3669 : 7 : bgw.bgw_restart_time = BGW_NEVER_RESTART;
3670 : 7 : snprintf(bgw.bgw_library_name, MAXPGPATH, "postgres");
3671 : 7 : snprintf(bgw.bgw_function_name, BGW_MAXLEN, "RepackWorkerMain");
93 3672 : 7 : bgw.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(decoding_worker->seg));
143 3673 : 7 : bgw.bgw_notify_pid = MyProcPid;
3674 : :
3675 [ - + ]: 7 : if (!RegisterDynamicBackgroundWorker(&bgw, &decoding_worker->handle))
143 alvherre@kurilemu.de 3676 [ # # ]:UBC 0 : ereport(ERROR,
3677 : : errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
3678 : : errmsg("out of background worker slots"),
3679 : : errhint("You might need to increase \"%s\".", "max_worker_processes"));
3680 : :
3681 : : /*
3682 : : * The decoding setup must be done before the caller can have XID assigned
3683 : : * for any reason, otherwise the worker might end up in a deadlock,
3684 : : * waiting for the caller's transaction to end. Therefore wait here until
3685 : : * the worker indicates that it has the logical decoding initialized.
3686 : : */
143 alvherre@kurilemu.de 3687 :CBC 7 : ConditionVariablePrepareToSleep(&shared->cv);
3688 : : for (;;)
3689 : 17 : {
3690 : : bool initialized;
3691 : :
3692 : 24 : SpinLockAcquire(&shared->mutex);
3693 : 24 : initialized = shared->initialized;
3694 : 24 : SpinLockRelease(&shared->mutex);
3695 : :
3696 [ + + ]: 24 : if (initialized)
3697 : 7 : break;
3698 : :
3699 : 17 : ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
3700 : : }
3701 : 7 : ConditionVariableCancelSleep();
3702 : 7 : }
3703 : :
3704 : : /*
3705 : : * Stop the decoding worker and cleanup the related resources.
3706 : : *
3707 : : * The worker stops on its own when it knows there is no more work to do, but
3708 : : * we need to stop it explicitly at least on ERROR in the launching backend.
3709 : : */
3710 : : static void
3711 : 7 : stop_repack_decoding_worker(void)
3712 : : {
3713 : : /* Nothing to do if no worker was set up. */
3714 [ - + ]: 7 : if (decoding_worker == NULL)
143 alvherre@kurilemu.de 3715 :UBC 0 : return;
3716 : :
3717 : : /* Terminate the worker process, if one is running. */
93 alvherre@kurilemu.de 3718 [ + - ]:CBC 7 : if (decoding_worker->handle != NULL)
3719 : : {
3720 : : BgwHandleStatus status;
3721 : :
3722 : 7 : TerminateBackgroundWorker(decoding_worker->handle);
3723 : : /* The worker should really exit before the REPACK command does. */
3724 : 7 : HOLD_INTERRUPTS();
3725 : 7 : status = WaitForBackgroundWorkerShutdown(decoding_worker->handle);
3726 [ - + ]: 7 : RESUME_INTERRUPTS();
3727 : :
3728 [ - + ]: 7 : if (status == BGWH_POSTMASTER_DIED)
93 alvherre@kurilemu.de 3729 [ # # ]:UBC 0 : ereport(FATAL,
3730 : : errcode(ERRCODE_ADMIN_SHUTDOWN),
3731 : : errmsg("postmaster exited during REPACK command"));
3732 : : }
3733 : :
3734 : : /*
3735 : : * Now detach from our shared memory segment. In error cases there might
3736 : : * still be messages from the worker in the queue, which ProcessInterrupts
3737 : : * would try to read; this is pointless (and causes an assertion failure),
3738 : : * so set the global pointer to NULL to have ProcessRepackMessages ignore
3739 : : * them.
3740 : : *
3741 : : * We must also cancel the current sleep, if one is still set up. This is
3742 : : * critical because the CV lives in the DSM that we're about to detach, so
3743 : : * if we omit it, later automatic cleanup tries to clear freed memory.
3744 : : */
93 alvherre@kurilemu.de 3745 [ + - ]:CBC 7 : if (decoding_worker->error_mqh != NULL)
3746 : 7 : shm_mq_detach(decoding_worker->error_mqh);
143 3747 : 7 : ConditionVariableCancelSleep();
93 3748 [ + - ]: 7 : if (decoding_worker->seg != NULL)
3749 : 7 : dsm_detach(decoding_worker->seg);
143 3750 : 7 : pfree(decoding_worker);
3751 : 7 : decoding_worker = NULL;
3752 : : }
3753 : :
3754 : : /* stop_repack_decoding_worker, wrapped as a before_shmem_exit callback */
3755 : : static void
100 alvherre@kurilemu.de 3756 :UBC 0 : stop_repack_decoding_worker_cb(int code, Datum arg)
3757 : : {
3758 : 0 : stop_repack_decoding_worker();
3759 : 0 : }
3760 : :
3761 : : /*
3762 : : * Get the initial snapshot from the decoding worker.
3763 : : */
3764 : : static Snapshot
143 alvherre@kurilemu.de 3765 :CBC 7 : get_initial_snapshot(DecodingWorker *worker)
3766 : : {
3767 : : DecodingWorkerShared *shared;
3768 : : char fname[MAXPGPATH];
3769 : : BufFile *file;
3770 : : Size snap_size;
3771 : : char *snap_space;
3772 : : Snapshot snapshot;
3773 : :
3774 : 7 : shared = (DecodingWorkerShared *) dsm_segment_address(worker->seg);
3775 : :
3776 : : /*
3777 : : * The worker needs to initialize the logical decoding, which usually
3778 : : * takes some time. Therefore it makes sense to prepare for the sleep
3779 : : * first.
3780 : : */
3781 : 7 : ConditionVariablePrepareToSleep(&shared->cv);
3782 : : for (;;)
3783 : 6 : {
3784 : : int last_exported;
3785 : :
3786 : 13 : SpinLockAcquire(&shared->mutex);
3787 : 13 : last_exported = shared->last_exported;
3788 : 13 : SpinLockRelease(&shared->mutex);
3789 : :
3790 : : /*
3791 : : * Has the worker exported the file we are waiting for?
3792 : : */
3793 [ + + ]: 13 : if (last_exported == WORKER_FILE_SNAPSHOT)
3794 : 7 : break;
3795 : :
3796 : 6 : ConditionVariableSleep(&shared->cv, WAIT_EVENT_REPACK_WORKER_EXPORT);
3797 : : }
3798 : 7 : ConditionVariableCancelSleep();
3799 : :
3800 : : /* Read the snapshot from a file. */
3801 : 7 : DecodingWorkerFileName(fname, shared->relid, WORKER_FILE_SNAPSHOT);
3802 : 7 : file = BufFileOpenFileSet(&shared->sfs.fs, fname, O_RDONLY, false);
3803 : 7 : BufFileReadExact(file, &snap_size, sizeof(snap_size));
3804 : 7 : snap_space = (char *) palloc(snap_size);
3805 : 7 : BufFileReadExact(file, snap_space, snap_size);
3806 : 7 : BufFileClose(file);
3807 : :
3808 : : /* Restore it. */
3809 : 7 : snapshot = RestoreSnapshot(snap_space);
3810 : 7 : pfree(snap_space);
3811 : :
3812 : 7 : return snapshot;
3813 : : }
3814 : :
3815 : : /*
3816 : : * Generate worker's file name into 'fname', which must be of size MAXPGPATH.
3817 : : * If relations of the same 'relid' happen to be processed at the same time,
3818 : : * they must be from different databases and therefore different backends must
3819 : : * be involved.
3820 : : */
3821 : : void
3822 : 42 : DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
3823 : : {
3824 : : /* The PID is already present in the fileset name, so we needn't add it */
3825 : 42 : snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
3826 : 42 : }
3827 : :
3828 : : /*
3829 : : * Handle receipt of an interrupt indicating a repack worker message.
3830 : : *
3831 : : * Note: this is called within a signal handler! All we can do is set
3832 : : * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
3833 : : * ProcessRepackMessages().
3834 : : */
3835 : : void
3836 : 7 : HandleRepackMessageInterrupt(void)
3837 : : {
3838 : 7 : InterruptPending = true;
3839 : 7 : RepackMessagePending = true;
3840 : 7 : SetLatch(MyLatch);
3841 : 7 : }
3842 : :
3843 : : /*
3844 : : * Process any queued protocol messages received from the repack worker.
3845 : : */
3846 : : void
3847 : 7 : ProcessRepackMessages(void)
3848 : : {
3849 : : MemoryContext oldcontext;
3850 : : static MemoryContext hpm_context = NULL;
3851 : :
3852 : : /*
3853 : : * Nothing to do if we haven't launched the worker yet or have already
3854 : : * terminated it.
3855 : : */
3856 [ - + ]: 7 : if (decoding_worker == NULL)
143 alvherre@kurilemu.de 3857 :UBC 0 : return;
3858 : :
3859 : : /*
3860 : : * This is invoked from ProcessInterrupts(), and since some of the
3861 : : * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
3862 : : * for recursive calls if more signals are received while this runs. It's
3863 : : * unclear that recursive entry would be safe, and it doesn't seem useful
3864 : : * even if it is safe, so let's block interrupts until done.
3865 : : */
143 alvherre@kurilemu.de 3866 :CBC 7 : HOLD_INTERRUPTS();
3867 : :
3868 : : /*
3869 : : * Moreover, CurrentMemoryContext might be pointing almost anywhere. We
3870 : : * don't want to risk leaking data into long-lived contexts, so let's do
3871 : : * our work here in a private context that we can reset on each use.
3872 : : */
3873 [ + + ]: 7 : if (hpm_context == NULL) /* first time through? */
3874 : 6 : hpm_context = AllocSetContextCreate(TopMemoryContext,
3875 : : "ProcessRepackMessages",
3876 : : ALLOCSET_DEFAULT_SIZES);
3877 : : else
3878 : 1 : MemoryContextReset(hpm_context);
3879 : :
3880 : 7 : oldcontext = MemoryContextSwitchTo(hpm_context);
3881 : :
3882 : : /* OK to process messages. Reset the flag saying there are more to do. */
3883 : 7 : RepackMessagePending = false;
3884 : :
3885 : : /*
3886 : : * Read as many messages as we can from the worker, but stop when no more
3887 : : * messages can be read from the worker without blocking.
3888 : : */
3889 : : while (true)
143 alvherre@kurilemu.de 3890 :UBC 0 : {
3891 : : shm_mq_result res;
3892 : : Size nbytes;
3893 : : void *data;
3894 : :
143 alvherre@kurilemu.de 3895 :CBC 7 : res = shm_mq_receive(decoding_worker->error_mqh, &nbytes,
3896 : : &data, true);
3897 [ - + ]: 7 : if (res == SHM_MQ_WOULD_BLOCK)
143 alvherre@kurilemu.de 3898 :UBC 0 : break;
143 alvherre@kurilemu.de 3899 [ - + ]:CBC 7 : else if (res == SHM_MQ_SUCCESS)
3900 : : {
3901 : : StringInfoData msg;
3902 : :
143 alvherre@kurilemu.de 3903 :UBC 0 : initStringInfo(&msg);
3904 : 0 : appendBinaryStringInfo(&msg, data, nbytes);
3905 : 0 : ProcessRepackMessage(&msg);
3906 : 0 : pfree(msg.data);
3907 : : }
3908 : : else
3909 : : {
3910 : : /*
3911 : : * The decoding worker is special in that it exits as soon as it
3912 : : * has its work done. Thus the DETACHED result code is fine.
3913 : : */
143 alvherre@kurilemu.de 3914 [ - + ]:CBC 7 : Assert(res == SHM_MQ_DETACHED);
3915 : :
3916 : 7 : break;
3917 : : }
3918 : : }
3919 : :
3920 : 7 : MemoryContextSwitchTo(oldcontext);
3921 : :
3922 : : /* Might as well clear the context on our way out */
3923 : 7 : MemoryContextReset(hpm_context);
3924 : :
3925 [ - + ]: 7 : RESUME_INTERRUPTS();
3926 : : }
3927 : :
3928 : : /*
3929 : : * Process a single protocol message received from a single parallel worker.
3930 : : */
3931 : : static void
143 alvherre@kurilemu.de 3932 :UBC 0 : ProcessRepackMessage(StringInfo msg)
3933 : : {
3934 : : char msgtype;
3935 : :
3936 : 0 : msgtype = pq_getmsgbyte(msg);
3937 : :
3938 [ # # ]: 0 : switch (msgtype)
3939 : : {
3940 : 0 : case PqMsg_ErrorResponse:
3941 : : case PqMsg_NoticeResponse:
3942 : : {
3943 : : ErrorData edata;
3944 : :
3945 : : /* Parse ErrorResponse or NoticeResponse. */
3946 : 0 : pq_parse_errornotice(msg, &edata);
3947 : :
3948 : : /* Death of a worker isn't enough justification for suicide. */
3949 : 0 : edata.elevel = Min(edata.elevel, ERROR);
3950 : :
3951 : : /*
3952 : : * Add a context line to show that this is a message
3953 : : * propagated from the worker. Otherwise, it can sometimes be
3954 : : * confusing to understand what actually happened.
3955 : : */
3956 [ # # ]: 0 : if (edata.context)
3957 : 0 : edata.context = psprintf("%s\n%s", edata.context,
3958 : : _("REPACK decoding worker"));
3959 : : else
3960 : 0 : edata.context = pstrdup(_("REPACK decoding worker"));
3961 : :
3962 : : /* Rethrow error or print notice. */
3963 : 0 : ThrowErrorData(&edata);
3964 : :
3965 : 0 : break;
3966 : : }
3967 : :
3968 : 0 : default:
3969 : : {
3970 [ # # ]: 0 : elog(ERROR, "unrecognized message type received from decoding worker: %c (message length %d bytes)",
3971 : : msgtype, msg->len);
3972 : : }
3973 : : }
3974 : 0 : }
|