/
balmaster
/
postgres
Обзор
Документация
Войти
/
balmaster
/
postgres
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/backend/replication/pgrepack/pgrepack.c
305 строк
9 KB
Álvaro Herrera
Disallow direct use of the pgrepack logical decoding plugin
09 июн 2026, 21:12
Не верифицирован
09 июн 2026, 21:12
cd7b204
Код
Авторство
О чём код?
/*------------------------------------------------------------------------- * * pgrepack.c * Logical Replication output plugin for REPACK command * * Copyright (c) 2026, PostgreSQL Global Development Group * * IDENTIFICATION * src/backend/replication/pgrepack/pgrepack.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/detoast.h" #include "commands/repack.h" #include "commands/repack_internal.h" #include "replication/snapbuild.h" #include "utils/memutils.h" PG_MODULE_MAGIC; static void repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, bool is_init); static void repack_shutdown(LogicalDecodingContext *ctx); static void repack_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn); static void repack_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr commit_lsn); static void repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation, ReorderBufferChange *change); static void repack_store_change(LogicalDecodingContext *ctx, Relation relation, ConcurrentChangeKind kind, HeapTuple tuple); void _PG_output_plugin_init(OutputPluginCallbacks *cb) { cb->startup_cb = repack_startup; cb->begin_cb = repack_begin_txn; cb->change_cb = repack_process_change; cb->commit_cb = repack_commit_txn; cb->shutdown_cb = repack_shutdown; } /* initialize this plugin */ static void repack_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, bool is_init) { RepackDecodingState *dstate; if (!AmRepackWorker()) ereport(ERROR, errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported use of logical decoding plugin \"%s\"", "pgrepack"), errdetail("This plugin can only be used by %s.", "REPACK (CONCURRENTLY)")); /* Initial setup of our private state */ Assert(CurrentMemoryContext == ctx->context); dstate = palloc0_object(RepackDecodingState); dstate->change_cxt = AllocSetContextCreate(ctx->context, "REPACK - change", ALLOCSET_DEFAULT_SIZES); /* repack_setup_logical_decoding fills in the rest */ ctx->output_writer_private = dstate; /* Probably unnecessary, as we don't use the SQL interface ... */ opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT; if (ctx->output_plugin_options != NIL) { ereport(ERROR, errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("this plugin does not expect any options")); } } static void repack_shutdown(LogicalDecodingContext *ctx) { } /* * As we don't release the slot during processing of particular table, there's * no room for SQL interface, even for debugging purposes. Therefore we need * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin * callbacks. (Although we might want to write custom callbacks, this API * seems to be unnecessarily generic for our purposes.) */ /* BEGIN callback */ static void repack_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) { } /* COMMIT callback */ static void repack_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, XLogRecPtr commit_lsn) { } /* * Callback for individual changed tuples */ static void repack_process_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, Relation relation, ReorderBufferChange *change) { RepackDecodingState *private PG_USED_FOR_ASSERTS_ONLY = (RepackDecodingState *) ctx->output_writer_private; /* Changes of other relation should not have been decoded. */ Assert(RelationGetRelid(relation) == private->relid); /* Decode entry depending on its type */ switch (change->action) { case REORDER_BUFFER_CHANGE_INSERT: { HeapTuple newtuple; newtuple = change->data.tp.newtuple; /* * Identity checks in the main function should have made this * impossible. */ if (newtuple == NULL) elog(ERROR, "incomplete insert info"); repack_store_change(ctx, relation, CHANGE_INSERT, newtuple); } break; case REORDER_BUFFER_CHANGE_UPDATE: { HeapTuple oldtuple, newtuple; oldtuple = change->data.tp.oldtuple; newtuple = change->data.tp.newtuple; if (newtuple == NULL) elog(ERROR, "incomplete update info"); if (oldtuple != NULL) repack_store_change(ctx, relation, CHANGE_UPDATE_OLD, oldtuple); repack_store_change(ctx, relation, CHANGE_UPDATE_NEW, newtuple); } break; case REORDER_BUFFER_CHANGE_DELETE: { HeapTuple oldtuple; oldtuple = change->data.tp.oldtuple; if (oldtuple == NULL) elog(ERROR, "incomplete delete info"); repack_store_change(ctx, relation, CHANGE_DELETE, oldtuple); } break; default: /* * Should not come here. This includes TRUNCATE of the table being * processed. heap_decode() cannot check the file locator easily, * but we assume that TRUNCATE uses AccessExclusiveLock on the * table so it should not occur during REPACK (CONCURRENTLY). */ Assert(false); break; } } /* * Write the given tuple, with the given change kind, to the repack spill * file. Later, the repack decoding worker can read these and replay * the operations on the new copy of the table. * * For each change affecting the table being repacked, we store enough * information about each tuple in it, so that it can be replayed in the * new copy of the table. */ static void repack_store_change(LogicalDecodingContext *ctx, Relation relation, ConcurrentChangeKind kind, HeapTuple tuple) { RepackDecodingState *dstate; MemoryContext oldcxt; BufFile *file; List *attrs_ext = NIL; int natt_ext; dstate = (RepackDecodingState *) ctx->output_writer_private; file = dstate->file; /* Store the change kind. */ BufFileWrite(file, &kind, 1); /* Use a frequently-reset context to avoid dealing with leaks manually */ oldcxt = MemoryContextSwitchTo(dstate->change_cxt); /* * If the tuple contains "external indirect" attributes, we need to write * the contents to the file because we have no control over that memory. */ if (HeapTupleHasExternal(tuple)) { TupleDesc desc = RelationGetDescr(relation); TupleTableSlot *slot; /* Initialize the slot, if not done already */ if (dstate->slot == NULL) { ResourceOwner saveResourceOwner; MemoryContextSwitchTo(dstate->worker_cxt); saveResourceOwner = CurrentResourceOwner; CurrentResourceOwner = dstate->worker_resowner; dstate->slot = MakeSingleTupleTableSlot(desc, &TTSOpsHeapTuple); MemoryContextSwitchTo(dstate->change_cxt); CurrentResourceOwner = saveResourceOwner; } slot = dstate->slot; ExecStoreHeapTuple(tuple, slot, false); /* * Loop over all attributes, and find out which ones we need to spill * separately, to wit: each one that's a non-null varlena and stored * out of line. */ for (int i = 0; i < desc->natts; i++) { CompactAttribute *attr = TupleDescCompactAttr(desc, i); varlena *varlen; if (attr->attisdropped || attr->attlen != -1 || slot_attisnull(slot, i + 1)) continue; slot_getsomeattrs(slot, i + 1); /* * This is a non-null varlena datum, but we only care if it's * out-of-line */ varlen = (varlena *) DatumGetPointer(slot->tts_values[i]); if (!VARATT_IS_EXTERNAL(varlen)) continue; /* * We spill any indirect-external attributes separately from the * heap tuple. Anything else is written as is. */ if (VARATT_IS_EXTERNAL_INDIRECT(varlen)) attrs_ext = lappend(attrs_ext, varlen); else { /* * Logical decoding should not produce "external expanded" * attributes (those actually should never appear on disk), so * only TOASTed attribute can be seen here. * * We get here if the table has external values but only * in-line values are being updated now. */ Assert(VARATT_IS_EXTERNAL_ONDISK(varlen)); } } ExecClearTuple(slot); } /* * First, write the original heap tuple, prefixed by its length. Note * that the external-toast tag for each toasted attribute will be present * in what we write, so that we know where to restore each one later. */ BufFileWrite(file, &tuple->t_len, sizeof(tuple->t_len)); BufFileWrite(file, tuple->t_data, tuple->t_len); /* Then, write the number of external attributes we found. */ natt_ext = list_length(attrs_ext); BufFileWrite(file, &natt_ext, sizeof(natt_ext)); /* Finally, the attributes themselves, if any */ foreach_ptr(varlena, attr_val, attrs_ext) { attr_val = detoast_external_attr(attr_val); BufFileWrite(file, attr_val, VARSIZE_ANY(attr_val)); /* These attributes could be large, so free them right away */ pfree(attr_val); } /* Cleanup. */ MemoryContextSwitchTo(oldcxt); MemoryContextReset(dstate->change_cxt); }