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