2008-12-08 04:32:10 +00:00
|
|
|
/*
|
2012-11-10 22:33:57 +00:00
|
|
|
* pg_repack: lib/repack.c
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
2011-04-29 05:06:48 +00:00
|
|
|
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
|
|
|
|
* Portions Copyright (c) 2011, Itagaki Takahiro
|
2012-11-11 03:00:00 +00:00
|
|
|
* Portions Copyright (c) 2012, The Reorg Development Team
|
2008-12-08 04:32:10 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "postgres.h"
|
2009-04-23 06:37:29 +00:00
|
|
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
#include "access/genam.h"
|
2009-01-21 08:09:22 +00:00
|
|
|
#include "access/transam.h"
|
|
|
|
#include "access/xact.h"
|
|
|
|
#include "catalog/dependency.h"
|
|
|
|
#include "catalog/indexing.h"
|
2008-12-08 04:32:10 +00:00
|
|
|
#include "catalog/namespace.h"
|
2009-01-21 08:09:22 +00:00
|
|
|
#include "catalog/pg_namespace.h"
|
2009-12-28 08:25:00 +00:00
|
|
|
#include "catalog/pg_opclass.h"
|
2009-01-19 04:28:21 +00:00
|
|
|
#include "catalog/pg_type.h"
|
2009-01-21 08:09:22 +00:00
|
|
|
#include "commands/tablecmds.h"
|
2008-12-08 04:32:10 +00:00
|
|
|
#include "commands/trigger.h"
|
|
|
|
#include "miscadmin.h"
|
2009-01-19 04:28:21 +00:00
|
|
|
#include "utils/builtins.h"
|
2008-12-08 04:32:10 +00:00
|
|
|
#include "utils/lsyscache.h"
|
2011-08-07 04:20:23 +00:00
|
|
|
#include "utils/rel.h"
|
2009-01-21 08:09:22 +00:00
|
|
|
#include "utils/relcache.h"
|
|
|
|
#include "utils/syscache.h"
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-07-02 09:50:58 +00:00
|
|
|
#include "pgut/pgut-spi.h"
|
2009-07-03 05:52:31 +00:00
|
|
|
#include "pgut/pgut-be.h"
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2012-09-20 03:48:53 +00:00
|
|
|
/* htup.h was reorganized for 9.3, so now we need this header */
|
|
|
|
#if PG_VERSION_NUM >= 90300
|
|
|
|
#include "access/htup_details.h"
|
|
|
|
#endif
|
|
|
|
|
2009-05-14 08:19:25 +00:00
|
|
|
PG_MODULE_MAGIC;
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2012-11-10 22:33:57 +00:00
|
|
|
extern Datum PGUT_EXPORT repack_version(PG_FUNCTION_ARGS);
|
|
|
|
extern Datum PGUT_EXPORT repack_trigger(PG_FUNCTION_ARGS);
|
|
|
|
extern Datum PGUT_EXPORT repack_apply(PG_FUNCTION_ARGS);
|
2012-12-09 11:35:52 +00:00
|
|
|
extern Datum PGUT_EXPORT repack_get_order_by(PG_FUNCTION_ARGS);
|
2012-11-10 22:33:57 +00:00
|
|
|
extern Datum PGUT_EXPORT repack_indexdef(PG_FUNCTION_ARGS);
|
|
|
|
extern Datum PGUT_EXPORT repack_swap(PG_FUNCTION_ARGS);
|
|
|
|
extern Datum PGUT_EXPORT repack_drop(PG_FUNCTION_ARGS);
|
|
|
|
extern Datum PGUT_EXPORT repack_disable_autovacuum(PG_FUNCTION_ARGS);
|
|
|
|
|
|
|
|
PG_FUNCTION_INFO_V1(repack_version);
|
|
|
|
PG_FUNCTION_INFO_V1(repack_trigger);
|
|
|
|
PG_FUNCTION_INFO_V1(repack_apply);
|
2012-12-09 11:35:52 +00:00
|
|
|
PG_FUNCTION_INFO_V1(repack_get_order_by);
|
2012-11-10 22:33:57 +00:00
|
|
|
PG_FUNCTION_INFO_V1(repack_indexdef);
|
|
|
|
PG_FUNCTION_INFO_V1(repack_swap);
|
|
|
|
PG_FUNCTION_INFO_V1(repack_drop);
|
|
|
|
PG_FUNCTION_INFO_V1(repack_disable_autovacuum);
|
|
|
|
|
|
|
|
static void repack_init(void);
|
|
|
|
static SPIPlanPtr repack_prepare(const char *src, int nargs, Oid *argtypes);
|
2009-01-19 04:28:21 +00:00
|
|
|
static const char *get_quoted_relname(Oid oid);
|
|
|
|
static const char *get_quoted_nspname(Oid oid);
|
2009-01-21 08:09:22 +00:00
|
|
|
static void swap_heap_or_index_files(Oid r1, Oid r2);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
#define copy_tuple(tuple, desc) \
|
|
|
|
PointerGetDatum(SPI_returntuple((tuple), (desc)))
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
#define IsToken(c) \
|
|
|
|
(IS_HIGHBIT_SET((c)) || isalnum((unsigned char) (c)) || (c) == '_')
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/* check access authority */
|
|
|
|
static void
|
|
|
|
must_be_superuser(const char *func)
|
|
|
|
{
|
|
|
|
if (!superuser())
|
|
|
|
elog(ERROR, "must be superuser to use %s function", func);
|
|
|
|
}
|
|
|
|
|
2012-05-01 06:11:49 +00:00
|
|
|
|
|
|
|
/* Include an implementation of RenameRelationInternal for old
|
|
|
|
* versions which don't have one.
|
|
|
|
*/
|
2008-12-08 04:32:10 +00:00
|
|
|
#if PG_VERSION_NUM < 80400
|
2009-01-21 08:09:22 +00:00
|
|
|
static void RenameRelationInternal(Oid myrelid, const char *newrelname, Oid namespaceId);
|
2008-12-08 04:32:10 +00:00
|
|
|
#endif
|
|
|
|
|
2012-05-01 06:11:49 +00:00
|
|
|
|
|
|
|
/* The API of RenameRelationInternal() was changed in 9.2.
|
|
|
|
* Use the RENAME_REL macro for compatibility across versions.
|
|
|
|
*/
|
|
|
|
#if PG_VERSION_NUM < 90200
|
|
|
|
#define RENAME_REL(relid, newrelname) RenameRelationInternal(relid, newrelname, PG_TOAST_NAMESPACE);
|
|
|
|
#else
|
|
|
|
#define RENAME_REL(relid, newrelname) RenameRelationInternal(relid, newrelname);
|
|
|
|
#endif
|
|
|
|
|
2012-11-11 18:30:27 +00:00
|
|
|
#ifdef REPACK_VERSION
|
|
|
|
/* macro trick to stringify a macro expansion */
|
|
|
|
#define xstr(s) str(s)
|
|
|
|
#define str(s) #s
|
|
|
|
#define LIBRARY_VERSION xstr(REPACK_VERSION)
|
|
|
|
#else
|
|
|
|
#define LIBRARY_VERSION "unknown"
|
|
|
|
#endif
|
2012-05-01 06:11:49 +00:00
|
|
|
|
2009-05-25 07:06:38 +00:00
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_version(PG_FUNCTION_ARGS)
|
2009-05-25 07:06:38 +00:00
|
|
|
{
|
2012-11-11 18:30:27 +00:00
|
|
|
return CStringGetTextDatum("pg_repack " LIBRARY_VERSION);
|
2009-05-25 07:06:38 +00:00
|
|
|
}
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/**
|
2012-11-10 22:33:57 +00:00
|
|
|
* @fn Datum repack_trigger(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
* @brief Insert a operation log into log-table.
|
|
|
|
*
|
2012-11-10 22:33:57 +00:00
|
|
|
* repack_trigger(sql)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
|
|
|
* @param sql SQL to insert a operation log into log-table.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_trigger(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
TriggerData *trigdata = (TriggerData *) fcinfo->context;
|
2009-01-21 08:09:22 +00:00
|
|
|
TupleDesc desc;
|
2008-12-08 04:32:10 +00:00
|
|
|
HeapTuple tuple;
|
|
|
|
Datum values[2];
|
2009-07-02 09:50:58 +00:00
|
|
|
bool nulls[2] = { 0, 0 };
|
2008-12-08 04:32:10 +00:00
|
|
|
Oid argtypes[2];
|
|
|
|
const char *sql;
|
|
|
|
|
|
|
|
/* authority check */
|
2012-11-10 22:33:57 +00:00
|
|
|
must_be_superuser("repack_trigger");
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* make sure it's called as a trigger at all */
|
|
|
|
if (!CALLED_AS_TRIGGER(fcinfo) ||
|
|
|
|
!TRIGGER_FIRED_BEFORE(trigdata->tg_event) ||
|
|
|
|
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event) ||
|
|
|
|
trigdata->tg_trigger->tgnargs != 1)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "repack_trigger: invalid trigger call");
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* retrieve parameters */
|
|
|
|
sql = trigdata->tg_trigger->tgargs[0];
|
2009-01-21 08:09:22 +00:00
|
|
|
desc = RelationGetDescr(trigdata->tg_relation);
|
2008-12-08 04:32:10 +00:00
|
|
|
argtypes[0] = argtypes[1] = trigdata->tg_relation->rd_rel->reltype;
|
|
|
|
|
|
|
|
/* connect to SPI manager */
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init();
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
/* INSERT: (NULL, newtup) */
|
|
|
|
tuple = trigdata->tg_trigtuple;
|
2009-07-02 09:50:58 +00:00
|
|
|
nulls[0] = true;
|
2009-01-21 08:09:22 +00:00
|
|
|
values[1] = copy_tuple(tuple, desc);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
|
|
|
|
{
|
|
|
|
/* DELETE: (oldtup, NULL) */
|
|
|
|
tuple = trigdata->tg_trigtuple;
|
2009-01-21 08:09:22 +00:00
|
|
|
values[0] = copy_tuple(tuple, desc);
|
2009-07-02 09:50:58 +00:00
|
|
|
nulls[1] = true;
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* UPDATE: (oldtup, newtup) */
|
|
|
|
tuple = trigdata->tg_newtuple;
|
2009-01-21 08:09:22 +00:00
|
|
|
values[0] = copy_tuple(trigdata->tg_trigtuple, desc);
|
|
|
|
values[1] = copy_tuple(tuple, desc);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
2012-11-10 22:33:57 +00:00
|
|
|
/* INSERT INTO repack.log VALUES ($1, $2) */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_args(SPI_OK_INSERT, sql, 2, argtypes, values, nulls);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
SPI_finish();
|
|
|
|
|
|
|
|
PG_RETURN_POINTER(tuple);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-11-10 22:33:57 +00:00
|
|
|
* @fn Datum repack_apply(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
* @brief Apply operations in log table into temp table.
|
|
|
|
*
|
2012-11-10 22:33:57 +00:00
|
|
|
* repack_apply(sql_peek, sql_insert, sql_delete, sql_update, sql_pop, count)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
|
|
|
* @param sql_peek SQL to pop tuple from log table.
|
|
|
|
* @param sql_insert SQL to insert into temp table.
|
|
|
|
* @param sql_delete SQL to delete from temp table.
|
|
|
|
* @param sql_update SQL to update temp table.
|
|
|
|
* @param sql_pop SQL to delete tuple from log table.
|
|
|
|
* @param count Max number of operations, or no count iff <=0.
|
|
|
|
* @retval Number of performed operations.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_apply(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
2009-07-02 09:50:58 +00:00
|
|
|
#define DEFAULT_PEEK_COUNT 1000
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
const char *sql_peek = PG_GETARG_CSTRING(0);
|
|
|
|
const char *sql_insert = PG_GETARG_CSTRING(1);
|
|
|
|
const char *sql_delete = PG_GETARG_CSTRING(2);
|
|
|
|
const char *sql_update = PG_GETARG_CSTRING(3);
|
|
|
|
const char *sql_pop = PG_GETARG_CSTRING(4);
|
|
|
|
int32 count = PG_GETARG_INT32(5);
|
|
|
|
|
|
|
|
SPIPlanPtr plan_peek = NULL;
|
|
|
|
SPIPlanPtr plan_insert = NULL;
|
|
|
|
SPIPlanPtr plan_delete = NULL;
|
|
|
|
SPIPlanPtr plan_update = NULL;
|
|
|
|
SPIPlanPtr plan_pop = NULL;
|
|
|
|
uint32 n, i;
|
|
|
|
Oid argtypes_peek[1] = { INT4OID };
|
|
|
|
Datum values_peek[1];
|
2009-07-02 09:50:58 +00:00
|
|
|
bool nulls_peek[1] = { 0 };
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* authority check */
|
2012-11-10 22:33:57 +00:00
|
|
|
must_be_superuser("repack_apply");
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* connect to SPI manager */
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init();
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* peek tuple in log */
|
2012-11-10 22:33:57 +00:00
|
|
|
plan_peek = repack_prepare(sql_peek, 1, argtypes_peek);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
for (n = 0;;)
|
|
|
|
{
|
|
|
|
int ntuples;
|
|
|
|
SPITupleTable *tuptable;
|
2009-07-02 09:50:58 +00:00
|
|
|
TupleDesc desc;
|
|
|
|
Oid argtypes[3]; /* id, pk, row */
|
|
|
|
Datum values[3]; /* id, pk, row */
|
|
|
|
bool nulls[3]; /* id, pk, row */
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* peek tuple in log */
|
|
|
|
if (count == 0)
|
2009-07-02 09:50:58 +00:00
|
|
|
values_peek[0] = Int32GetDatum(DEFAULT_PEEK_COUNT);
|
2008-12-08 04:32:10 +00:00
|
|
|
else
|
2009-07-02 09:50:58 +00:00
|
|
|
values_peek[0] = Int32GetDatum(Min(count - n, DEFAULT_PEEK_COUNT));
|
2009-05-14 08:19:25 +00:00
|
|
|
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_plan(SPI_OK_SELECT, plan_peek, values_peek, nulls_peek);
|
2008-12-08 04:32:10 +00:00
|
|
|
if (SPI_processed <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* copy tuptable because we will call other sqls. */
|
|
|
|
ntuples = SPI_processed;
|
|
|
|
tuptable = SPI_tuptable;
|
2009-01-21 08:09:22 +00:00
|
|
|
desc = tuptable->tupdesc;
|
|
|
|
argtypes[0] = SPI_gettypeid(desc, 1); /* id */
|
|
|
|
argtypes[1] = SPI_gettypeid(desc, 2); /* pk */
|
|
|
|
argtypes[2] = SPI_gettypeid(desc, 3); /* row */
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
for (i = 0; i < ntuples; i++, n++)
|
|
|
|
{
|
|
|
|
HeapTuple tuple;
|
2009-05-14 08:19:25 +00:00
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
tuple = tuptable->vals[i];
|
2009-07-02 09:50:58 +00:00
|
|
|
values[0] = SPI_getbinval(tuple, desc, 1, &nulls[0]);
|
|
|
|
values[1] = SPI_getbinval(tuple, desc, 2, &nulls[1]);
|
|
|
|
values[2] = SPI_getbinval(tuple, desc, 3, &nulls[2]);
|
|
|
|
|
|
|
|
if (nulls[1])
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
/* INSERT */
|
|
|
|
if (plan_insert == NULL)
|
2012-11-10 22:33:57 +00:00
|
|
|
plan_insert = repack_prepare(sql_insert, 1, &argtypes[2]);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_plan(SPI_OK_INSERT, plan_insert, &values[2], &nulls[2]);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
2009-07-02 09:50:58 +00:00
|
|
|
else if (nulls[2])
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
/* DELETE */
|
|
|
|
if (plan_delete == NULL)
|
2012-11-10 22:33:57 +00:00
|
|
|
plan_delete = repack_prepare(sql_delete, 1, &argtypes[1]);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_plan(SPI_OK_DELETE, plan_delete, &values[1], &nulls[1]);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* UPDATE */
|
|
|
|
if (plan_update == NULL)
|
2012-11-10 22:33:57 +00:00
|
|
|
plan_update = repack_prepare(sql_update, 2, &argtypes[1]);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_plan(SPI_OK_UPDATE, plan_update, &values[1], &nulls[1]);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* delete tuple in log */
|
|
|
|
if (plan_pop == NULL)
|
2012-11-10 22:33:57 +00:00
|
|
|
plan_pop = repack_prepare(sql_pop, 1, argtypes);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_plan(SPI_OK_DELETE, plan_pop, values, nulls);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
SPI_freetuptable(tuptable);
|
|
|
|
}
|
|
|
|
|
|
|
|
SPI_finish();
|
|
|
|
|
|
|
|
PG_RETURN_INT32(n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2012-09-21 09:15:47 +09:00
|
|
|
* Parsed CREATE INDEX statement. You can rebuild sql using
|
2008-12-08 04:32:10 +00:00
|
|
|
* sprintf(buf, "%s %s ON %s USING %s (%s)%s",
|
|
|
|
* create, index, table type, columns, options)
|
|
|
|
*/
|
|
|
|
typedef struct IndexDef
|
|
|
|
{
|
|
|
|
char *create; /* CREATE INDEX or CREATE UNIQUE INDEX */
|
|
|
|
char *index; /* index name including schema */
|
|
|
|
char *table; /* table name including schema */
|
|
|
|
char *type; /* btree, hash, gist or gin */
|
|
|
|
char *columns; /* column definition */
|
|
|
|
char *options; /* options after columns. WITH, TABLESPACE and WHERE */
|
|
|
|
} IndexDef;
|
|
|
|
|
|
|
|
static char *
|
2009-05-25 07:06:38 +00:00
|
|
|
get_relation_name(Oid relid)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
Oid nsp = get_rel_namespace(relid);
|
|
|
|
char *nspname;
|
|
|
|
|
|
|
|
/* Qualify the name if not visible in search path */
|
|
|
|
if (RelationIsVisible(relid))
|
|
|
|
nspname = NULL;
|
|
|
|
else
|
|
|
|
nspname = get_namespace_name(nsp);
|
|
|
|
|
|
|
|
return quote_qualified_identifier(nspname, get_rel_name(relid));
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
parse_error(Oid index)
|
|
|
|
{
|
2009-07-02 09:50:58 +00:00
|
|
|
elog(ERROR, "unexpected index definition: %s", pg_get_indexdef_string(index));
|
2008-12-08 04:32:10 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
skip_const(Oid index, char *sql, const char *arg1, const char *arg2)
|
|
|
|
{
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
if ((arg1 && strncmp(sql, arg1, (len = strlen(arg1))) == 0) ||
|
|
|
|
(arg2 && strncmp(sql, arg2, (len = strlen(arg2))) == 0))
|
|
|
|
{
|
|
|
|
sql[len] = '\0';
|
|
|
|
return sql + len + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* error */
|
|
|
|
return parse_error(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
|
|
|
skip_ident(Oid index, char *sql)
|
|
|
|
{
|
2009-12-28 08:25:00 +00:00
|
|
|
while (*sql && isspace((unsigned char) *sql))
|
|
|
|
sql++;
|
|
|
|
|
|
|
|
if (*sql == '"')
|
|
|
|
{
|
|
|
|
sql++;
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
char *end = strchr(sql, '"');
|
|
|
|
if (end == NULL)
|
|
|
|
return parse_error(index);
|
|
|
|
else if (end[1] != '"')
|
|
|
|
{
|
|
|
|
end[1] = '\0';
|
|
|
|
return end + 2;
|
|
|
|
}
|
|
|
|
else /* escaped quote ("") */
|
|
|
|
sql = end + 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
2009-12-28 08:25:00 +00:00
|
|
|
while (*sql && IsToken(*sql))
|
|
|
|
sql++;
|
2008-12-08 04:32:10 +00:00
|
|
|
*sql = '\0';
|
2009-12-28 08:25:00 +00:00
|
|
|
return sql + 1;
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* error */
|
|
|
|
return parse_error(index);
|
|
|
|
}
|
|
|
|
|
2010-02-05 03:24:22 +00:00
|
|
|
/*
|
|
|
|
* Skip until 'end' character found. The 'end' character is replaced with \0.
|
|
|
|
* Returns the next character of the 'end', or NULL if 'end' is not found.
|
|
|
|
*/
|
2008-12-08 04:32:10 +00:00
|
|
|
static char *
|
2009-12-28 08:25:00 +00:00
|
|
|
skip_until(Oid index, char *sql, char end)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
char instr = 0;
|
2009-12-28 08:25:00 +00:00
|
|
|
int nopen = 0;
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
for (; *sql && (nopen > 0 || instr != 0 || *sql != end); sql++)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
if (instr)
|
|
|
|
{
|
|
|
|
if (sql[0] == instr)
|
|
|
|
{
|
|
|
|
if (sql[1] == instr)
|
|
|
|
sql++;
|
|
|
|
else
|
|
|
|
instr = 0;
|
|
|
|
}
|
|
|
|
else if (sql[0] == '\\')
|
2009-12-28 08:25:00 +00:00
|
|
|
sql++; /* next char is always string */
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
switch (sql[0])
|
|
|
|
{
|
|
|
|
case '(':
|
|
|
|
nopen++;
|
|
|
|
break;
|
|
|
|
case ')':
|
|
|
|
nopen--;
|
|
|
|
break;
|
|
|
|
case '\'':
|
|
|
|
case '"':
|
|
|
|
instr = sql[0];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
if (nopen == 0 && instr == 0)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
2010-02-05 03:24:22 +00:00
|
|
|
if (*sql)
|
|
|
|
{
|
|
|
|
*sql = '\0';
|
|
|
|
return sql + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return NULL;
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* error */
|
|
|
|
return parse_error(index);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
parse_indexdef(IndexDef *stmt, Oid index, Oid table)
|
|
|
|
{
|
|
|
|
char *sql = pg_get_indexdef_string(index);
|
2009-01-19 04:28:21 +00:00
|
|
|
const char *idxname = get_quoted_relname(index);
|
2009-05-25 07:06:38 +00:00
|
|
|
const char *tblname = get_relation_name(table);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* CREATE [UNIQUE] INDEX */
|
|
|
|
stmt->create = sql;
|
|
|
|
sql = skip_const(index, sql, "CREATE INDEX", "CREATE UNIQUE INDEX");
|
|
|
|
/* index */
|
|
|
|
stmt->index = sql;
|
|
|
|
sql = skip_const(index, sql, idxname, NULL);
|
|
|
|
/* ON */
|
|
|
|
sql = skip_const(index, sql, "ON", NULL);
|
|
|
|
/* table */
|
|
|
|
stmt->table = sql;
|
|
|
|
sql = skip_const(index, sql, tblname, NULL);
|
|
|
|
/* USING */
|
|
|
|
sql = skip_const(index, sql, "USING", NULL);
|
|
|
|
/* type */
|
2009-12-28 08:25:00 +00:00
|
|
|
stmt->type = sql;
|
2008-12-08 04:32:10 +00:00
|
|
|
sql = skip_ident(index, sql);
|
|
|
|
/* (columns) */
|
2009-12-28 08:25:00 +00:00
|
|
|
if ((sql = strchr(sql, '(')) == NULL)
|
|
|
|
parse_error(index);
|
|
|
|
sql++;
|
2008-12-08 04:32:10 +00:00
|
|
|
stmt->columns = sql;
|
2010-02-05 03:24:22 +00:00
|
|
|
if ((sql = skip_until(index, sql, ')')) == NULL)
|
|
|
|
parse_error(index);
|
2008-12-08 04:32:10 +00:00
|
|
|
/* options */
|
|
|
|
stmt->options = sql;
|
|
|
|
}
|
|
|
|
|
2012-12-09 01:11:39 +00:00
|
|
|
/*
|
|
|
|
* Parse the trailing ... [ DESC ] [ NULLS { FIRST | LAST } ] from an index
|
|
|
|
* definition column.
|
|
|
|
* Returned values point to token. \0's are inserted to separate parsed parts.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
parse_desc_nulls(char *token, char **desc, char **nulls)
|
|
|
|
{
|
|
|
|
#if PG_VERSION_NUM >= 80300
|
|
|
|
char *pos;
|
|
|
|
|
|
|
|
/* easier to walk backwards than to parse quotes and escapes... */
|
|
|
|
if (NULL != (pos = strstr(token, " NULLS FIRST")))
|
|
|
|
{
|
|
|
|
*nulls = pos + 1;
|
|
|
|
*pos = '\0';
|
|
|
|
}
|
|
|
|
else if (NULL != (pos = strstr(token, " NULLS LAST")))
|
|
|
|
{
|
|
|
|
*nulls = pos + 1;
|
|
|
|
*pos = '\0';
|
|
|
|
}
|
|
|
|
if (NULL != (pos = strstr(token, " DESC")))
|
|
|
|
{
|
|
|
|
*desc = pos + 1;
|
|
|
|
*pos = '\0';
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/**
|
2012-12-09 11:35:52 +00:00
|
|
|
* @fn Datum repack_get_order_by(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
* @brief Get key definition of the index.
|
|
|
|
*
|
2012-12-09 11:35:52 +00:00
|
|
|
* repack_get_order_by(index, table)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
|
|
|
* @param index Oid of target index.
|
|
|
|
* @param table Oid of table of the index.
|
|
|
|
* @retval Create index DDL for temp table.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-12-09 11:35:52 +00:00
|
|
|
repack_get_order_by(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
Oid index = PG_GETARG_OID(0);
|
|
|
|
Oid table = PG_GETARG_OID(1);
|
|
|
|
IndexDef stmt;
|
2009-12-28 08:25:00 +00:00
|
|
|
char *token;
|
|
|
|
char *next;
|
|
|
|
StringInfoData str;
|
|
|
|
Relation indexRel = NULL;
|
|
|
|
int nattr;
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
parse_indexdef(&stmt, index, table);
|
2010-04-21 09:25:20 +00:00
|
|
|
elog(DEBUG2, "indexdef.create = %s", stmt.create);
|
|
|
|
elog(DEBUG2, "indexdef.index = %s", stmt.index);
|
|
|
|
elog(DEBUG2, "indexdef.table = %s", stmt.table);
|
|
|
|
elog(DEBUG2, "indexdef.type = %s", stmt.type);
|
|
|
|
elog(DEBUG2, "indexdef.columns = %s", stmt.columns);
|
|
|
|
elog(DEBUG2, "indexdef.options = %s", stmt.options);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
/*
|
|
|
|
* FIXME: this is very unreliable implementation but I don't want to
|
|
|
|
* re-implement customized versions of pg_get_indexdef_string...
|
|
|
|
*/
|
|
|
|
|
|
|
|
initStringInfo(&str);
|
2010-02-05 03:24:22 +00:00
|
|
|
for (nattr = 0, next = stmt.columns; next; nattr++)
|
2009-12-28 08:25:00 +00:00
|
|
|
{
|
|
|
|
char *opcname;
|
2012-12-09 01:11:39 +00:00
|
|
|
char *coldesc = NULL;
|
|
|
|
char *colnulls = NULL;
|
2009-12-28 08:25:00 +00:00
|
|
|
|
|
|
|
token = next;
|
2010-04-21 09:25:20 +00:00
|
|
|
while (isspace((unsigned char) *token))
|
|
|
|
token++;
|
2009-12-28 08:25:00 +00:00
|
|
|
next = skip_until(index, next, ',');
|
2012-12-09 01:11:39 +00:00
|
|
|
parse_desc_nulls(token, &coldesc, &colnulls);
|
2010-06-14 05:11:26 +00:00
|
|
|
opcname = skip_until(index, token, ' ');
|
2012-12-09 01:11:39 +00:00
|
|
|
appendStringInfoString(&str, token);
|
|
|
|
if (coldesc)
|
|
|
|
appendStringInfo(&str, " %s", coldesc);
|
2010-06-14 05:11:26 +00:00
|
|
|
if (opcname)
|
2009-12-28 08:25:00 +00:00
|
|
|
{
|
|
|
|
/* lookup default operator name from operator class */
|
|
|
|
|
|
|
|
Oid opclass;
|
|
|
|
Oid oprid;
|
|
|
|
int16 strategy = BTLessStrategyNumber;
|
|
|
|
Oid opcintype;
|
|
|
|
Oid opfamily;
|
|
|
|
HeapTuple tp;
|
|
|
|
Form_pg_opclass opclassTup;
|
2012-09-21 09:15:47 +09:00
|
|
|
|
2009-12-28 08:25:00 +00:00
|
|
|
opclass = OpclassnameGetOpcid(BTREE_AM_OID, opcname);
|
|
|
|
|
|
|
|
/* Retrieve operator information. */
|
|
|
|
tp = SearchSysCache(CLAOID, ObjectIdGetDatum(opclass), 0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(tp))
|
|
|
|
elog(ERROR, "cache lookup failed for opclass %u", opclass);
|
|
|
|
opclassTup = (Form_pg_opclass) GETSTRUCT(tp);
|
|
|
|
opfamily = opclassTup->opcfamily;
|
|
|
|
opcintype = opclassTup->opcintype;
|
|
|
|
ReleaseSysCache(tp);
|
|
|
|
|
|
|
|
if (!OidIsValid(opcintype))
|
|
|
|
{
|
|
|
|
if (indexRel == NULL)
|
|
|
|
indexRel = index_open(index, NoLock);
|
|
|
|
|
|
|
|
opcintype = RelationGetDescr(indexRel)->attrs[nattr]->atttypid;
|
|
|
|
}
|
|
|
|
|
|
|
|
oprid = get_opfamily_member(opfamily, opcintype, opcintype, strategy);
|
|
|
|
if (!OidIsValid(oprid))
|
|
|
|
elog(ERROR, "missing operator %d(%u,%u) in opfamily %u",
|
|
|
|
strategy, opcintype, opcintype, opfamily);
|
|
|
|
|
2012-10-17 08:00:47 -07:00
|
|
|
opcname[-1] = '\0';
|
2012-12-09 01:11:39 +00:00
|
|
|
appendStringInfo(&str, " USING %s", get_opname(oprid));
|
2009-12-28 08:25:00 +00:00
|
|
|
}
|
2012-12-09 01:11:39 +00:00
|
|
|
if (colnulls)
|
|
|
|
appendStringInfo(&str, " %s", colnulls);
|
2010-02-05 03:24:22 +00:00
|
|
|
if (next)
|
2010-04-21 09:25:20 +00:00
|
|
|
appendStringInfoString(&str, ", ");
|
2009-12-28 08:25:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (indexRel != NULL)
|
|
|
|
index_close(indexRel, NoLock);
|
|
|
|
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(str.data));
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-11-10 22:33:57 +00:00
|
|
|
* @fn Datum repack_indexdef(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
* @brief Reproduce DDL that create index at the temp table.
|
|
|
|
*
|
2012-11-10 22:33:57 +00:00
|
|
|
* repack_indexdef(index, table)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
|
|
|
* @param index Oid of target index.
|
|
|
|
* @param table Oid of table of the index.
|
|
|
|
* @retval Create index DDL for temp table.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_indexdef(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
Oid index = PG_GETARG_OID(0);
|
|
|
|
Oid table = PG_GETARG_OID(1);
|
|
|
|
IndexDef stmt;
|
|
|
|
StringInfoData str;
|
|
|
|
|
|
|
|
parse_indexdef(&stmt, index, table);
|
|
|
|
initStringInfo(&str);
|
2012-11-10 22:33:57 +00:00
|
|
|
appendStringInfo(&str, "%s index_%u ON repack.table_%u USING %s (%s)%s",
|
2008-12-08 04:32:10 +00:00
|
|
|
stmt.create, index, table, stmt.type, stmt.columns, stmt.options);
|
|
|
|
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(str.data));
|
|
|
|
}
|
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
static Oid
|
|
|
|
getoid(HeapTuple tuple, TupleDesc desc, int column)
|
|
|
|
{
|
|
|
|
bool isnull;
|
|
|
|
Datum datum = SPI_getbinval(tuple, desc, column, &isnull);
|
|
|
|
return isnull ? InvalidOid : DatumGetObjectId(datum);
|
|
|
|
}
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/**
|
2012-11-10 22:33:57 +00:00
|
|
|
* @fn Datum repack_swap(PG_FUNCTION_ARGS)
|
2009-01-21 08:09:22 +00:00
|
|
|
* @brief Swapping relfilenode of tables and relation ids of toast tables
|
|
|
|
* and toast indexes.
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
2012-11-10 22:33:57 +00:00
|
|
|
* repack_swap(oid, relname)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
2009-07-02 09:50:58 +00:00
|
|
|
* TODO: remove useless CommandCounterIncrement().
|
|
|
|
*
|
2008-12-08 04:32:10 +00:00
|
|
|
* @param oid Oid of table of target.
|
|
|
|
* @retval None.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_swap(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
2009-01-21 08:09:22 +00:00
|
|
|
Oid oid = PG_GETARG_OID(0);
|
|
|
|
const char *relname = get_quoted_relname(oid);
|
|
|
|
const char *nspname = get_quoted_nspname(oid);
|
|
|
|
Oid argtypes[1] = { OIDOID };
|
2009-07-02 09:50:58 +00:00
|
|
|
bool nulls[1] = { 0 };
|
2009-01-21 08:09:22 +00:00
|
|
|
Datum values[1];
|
|
|
|
SPITupleTable *tuptable;
|
|
|
|
TupleDesc desc;
|
|
|
|
HeapTuple tuple;
|
|
|
|
uint32 records;
|
|
|
|
uint32 i;
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
Oid reltoastrelid1;
|
|
|
|
Oid reltoastidxid1;
|
|
|
|
Oid oid2;
|
|
|
|
Oid reltoastrelid2;
|
|
|
|
Oid reltoastidxid2;
|
2009-05-14 08:19:25 +00:00
|
|
|
Oid owner1;
|
|
|
|
Oid owner2;
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* authority check */
|
2012-11-10 22:33:57 +00:00
|
|
|
must_be_superuser("repack_swap");
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* connect to SPI manager */
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init();
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
/* swap relfilenode and dependencies for tables. */
|
|
|
|
values[0] = ObjectIdGetDatum(oid);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_args(SPI_OK_SELECT,
|
2009-05-14 08:19:25 +00:00
|
|
|
"SELECT X.reltoastrelid, TX.reltoastidxid, X.relowner,"
|
2012-09-21 09:15:47 +09:00
|
|
|
" Y.oid, Y.reltoastrelid, TY.reltoastidxid, Y.relowner"
|
2009-05-14 08:19:25 +00:00
|
|
|
" FROM pg_catalog.pg_class X LEFT JOIN pg_catalog.pg_class TX"
|
|
|
|
" ON X.reltoastrelid = TX.oid,"
|
|
|
|
" pg_catalog.pg_class Y LEFT JOIN pg_catalog.pg_class TY"
|
|
|
|
" ON Y.reltoastrelid = TY.oid"
|
2009-01-21 08:09:22 +00:00
|
|
|
" WHERE X.oid = $1"
|
2012-11-10 22:33:57 +00:00
|
|
|
" AND Y.oid = ('repack.table_' || X.oid)::regclass",
|
2009-07-02 09:50:58 +00:00
|
|
|
1, argtypes, values, nulls);
|
2009-01-21 08:09:22 +00:00
|
|
|
|
|
|
|
tuptable = SPI_tuptable;
|
|
|
|
desc = tuptable->tupdesc;
|
|
|
|
records = SPI_processed;
|
|
|
|
|
|
|
|
if (records == 0)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "repack_swap : no swap target");
|
2009-01-21 08:09:22 +00:00
|
|
|
|
|
|
|
tuple = tuptable->vals[0];
|
|
|
|
|
2009-05-14 08:19:25 +00:00
|
|
|
reltoastrelid1 = getoid(tuple, desc, 1);
|
|
|
|
reltoastidxid1 = getoid(tuple, desc, 2);
|
|
|
|
owner1 = getoid(tuple, desc, 3);
|
2009-01-21 08:09:22 +00:00
|
|
|
oid2 = getoid(tuple, desc, 4);
|
|
|
|
reltoastrelid2 = getoid(tuple, desc, 5);
|
|
|
|
reltoastidxid2 = getoid(tuple, desc, 6);
|
2009-05-14 08:19:25 +00:00
|
|
|
owner2 = getoid(tuple, desc, 7);
|
2009-01-21 08:09:22 +00:00
|
|
|
|
2009-05-14 08:19:25 +00:00
|
|
|
/* change owner of new relation to original owner */
|
|
|
|
if (owner1 != owner2)
|
|
|
|
{
|
2011-01-06 09:35:15 +00:00
|
|
|
ATExecChangeOwner(oid2, owner1, true, AccessExclusiveLock);
|
2009-05-14 08:19:25 +00:00
|
|
|
CommandCounterIncrement();
|
|
|
|
}
|
|
|
|
|
2009-07-02 09:50:58 +00:00
|
|
|
/* swap tables. */
|
2009-01-21 08:09:22 +00:00
|
|
|
swap_heap_or_index_files(oid, oid2);
|
|
|
|
CommandCounterIncrement();
|
|
|
|
|
2009-07-02 09:50:58 +00:00
|
|
|
/* swap indexes. */
|
2008-12-08 04:32:10 +00:00
|
|
|
values[0] = ObjectIdGetDatum(oid);
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_args(SPI_OK_SELECT,
|
2009-01-21 08:09:22 +00:00
|
|
|
"SELECT X.oid, Y.oid"
|
2009-05-14 08:19:25 +00:00
|
|
|
" FROM pg_catalog.pg_index I,"
|
|
|
|
" pg_catalog.pg_class X,"
|
|
|
|
" pg_catalog.pg_class Y"
|
2009-01-21 08:09:22 +00:00
|
|
|
" WHERE I.indrelid = $1"
|
|
|
|
" AND I.indexrelid = X.oid"
|
2012-10-16 23:29:36 +01:00
|
|
|
" AND I.indisvalid"
|
2012-11-10 22:33:57 +00:00
|
|
|
" AND Y.oid = ('repack.index_' || X.oid)::regclass",
|
2009-07-02 09:50:58 +00:00
|
|
|
1, argtypes, values, nulls);
|
2009-01-21 08:09:22 +00:00
|
|
|
|
|
|
|
tuptable = SPI_tuptable;
|
|
|
|
desc = tuptable->tupdesc;
|
|
|
|
records = SPI_processed;
|
|
|
|
|
|
|
|
for (i = 0; i < records; i++)
|
|
|
|
{
|
2009-07-02 09:50:58 +00:00
|
|
|
Oid idx1, idx2;
|
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
tuple = tuptable->vals[i];
|
2009-07-02 09:50:58 +00:00
|
|
|
idx1 = getoid(tuple, desc, 1);
|
|
|
|
idx2 = getoid(tuple, desc, 2);
|
|
|
|
swap_heap_or_index_files(idx1, idx2);
|
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
CommandCounterIncrement();
|
|
|
|
}
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
/* swap names for toast tables and toast indexes */
|
2010-04-21 09:25:20 +00:00
|
|
|
if (reltoastrelid1 == InvalidOid)
|
|
|
|
{
|
|
|
|
if (reltoastidxid1 != InvalidOid ||
|
|
|
|
reltoastrelid2 != InvalidOid ||
|
|
|
|
reltoastidxid2 != InvalidOid)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "repack_swap : unexpected toast relations (T1=%u, I1=%u, T2=%u, I2=%u",
|
2010-04-21 09:25:20 +00:00
|
|
|
reltoastrelid1, reltoastidxid1, reltoastrelid2, reltoastidxid2);
|
|
|
|
/* do nothing */
|
|
|
|
}
|
|
|
|
else if (reltoastrelid2 == InvalidOid)
|
|
|
|
{
|
|
|
|
char name[NAMEDATALEN];
|
|
|
|
|
|
|
|
if (reltoastidxid1 == InvalidOid ||
|
|
|
|
reltoastidxid2 != InvalidOid)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "repack_swap : unexpected toast relations (T1=%u, I1=%u, T2=%u, I2=%u",
|
2010-04-21 09:25:20 +00:00
|
|
|
reltoastrelid1, reltoastidxid1, reltoastrelid2, reltoastidxid2);
|
|
|
|
|
|
|
|
/* rename X to Y */
|
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u", oid2);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastrelid1, name);
|
2010-04-21 09:25:20 +00:00
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u_index", oid2);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastidxid1, name);
|
2010-04-21 09:25:20 +00:00
|
|
|
CommandCounterIncrement();
|
|
|
|
}
|
|
|
|
else if (reltoastrelid1 != InvalidOid)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
2009-01-21 08:09:22 +00:00
|
|
|
char name[NAMEDATALEN];
|
|
|
|
int pid = getpid();
|
2009-01-23 02:33:11 +00:00
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
/* rename X to TEMP */
|
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_pid%d", pid);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastrelid1, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_pid%d_index", pid);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastidxid1, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
CommandCounterIncrement();
|
|
|
|
|
|
|
|
/* rename Y to X */
|
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u", oid);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastrelid2, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u_index", oid);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastidxid2, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
CommandCounterIncrement();
|
|
|
|
|
|
|
|
/* rename TEMP to Y */
|
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u", oid2);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastrelid1, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
snprintf(name, NAMEDATALEN, "pg_toast_%u_index", oid2);
|
2012-05-01 06:11:49 +00:00
|
|
|
RENAME_REL(reltoastidxid1, name);
|
2009-01-21 08:09:22 +00:00
|
|
|
CommandCounterIncrement();
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
2012-11-10 22:33:57 +00:00
|
|
|
/* drop repack trigger */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2009-01-19 04:28:21 +00:00
|
|
|
SPI_OK_UTILITY,
|
2012-11-10 22:33:57 +00:00
|
|
|
"DROP TRIGGER IF EXISTS z_repack_trigger ON %s.%s CASCADE",
|
2009-01-19 04:28:21 +00:00
|
|
|
nspname, relname);
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
SPI_finish();
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2012-11-10 22:33:57 +00:00
|
|
|
* @fn Datum repack_drop(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
* @brief Delete temporarily objects.
|
|
|
|
*
|
2012-11-10 22:33:57 +00:00
|
|
|
* repack_drop(oid, relname)
|
2008-12-08 04:32:10 +00:00
|
|
|
*
|
|
|
|
* @param oid Oid of target table.
|
|
|
|
* @retval None.
|
|
|
|
*/
|
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_drop(PG_FUNCTION_ARGS)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
Oid oid = PG_GETARG_OID(0);
|
2009-01-19 04:28:21 +00:00
|
|
|
const char *relname = get_quoted_relname(oid);
|
|
|
|
const char *nspname = get_quoted_nspname(oid);
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2012-11-11 15:24:38 -05:00
|
|
|
if (!(relname && nspname))
|
|
|
|
{
|
|
|
|
elog(ERROR, "table name not found for OID %u", oid);
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/* authority check */
|
2012-11-10 22:33:57 +00:00
|
|
|
must_be_superuser("repack_drop");
|
2008-12-08 04:32:10 +00:00
|
|
|
|
|
|
|
/* connect to SPI manager */
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init();
|
2008-12-08 04:32:10 +00:00
|
|
|
|
2009-01-19 04:28:21 +00:00
|
|
|
/*
|
2012-11-10 22:33:57 +00:00
|
|
|
* drop repack trigger: We have already dropped the trigger in normal
|
2009-01-19 04:28:21 +00:00
|
|
|
* cases, but it can be left on error.
|
|
|
|
*/
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2008-12-08 04:32:10 +00:00
|
|
|
SPI_OK_UTILITY,
|
2012-11-10 22:33:57 +00:00
|
|
|
"DROP TRIGGER IF EXISTS z_repack_trigger ON %s.%s CASCADE",
|
2008-12-08 04:32:10 +00:00
|
|
|
nspname, relname);
|
|
|
|
|
2009-05-25 07:06:38 +00:00
|
|
|
#if PG_VERSION_NUM < 80400
|
|
|
|
/* delete autovacuum settings */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2009-05-25 07:06:38 +00:00
|
|
|
SPI_OK_DELETE,
|
|
|
|
"DELETE FROM pg_catalog.pg_autovacuum v"
|
|
|
|
" USING pg_class c, pg_namespace n"
|
|
|
|
" WHERE relname IN ('log_%u', 'table_%u')"
|
2012-11-10 22:33:57 +00:00
|
|
|
" AND n.nspname = 'repack'"
|
2009-05-25 07:06:38 +00:00
|
|
|
" AND c.relnamespace = n.oid"
|
|
|
|
" AND v.vacrelid = c.oid",
|
|
|
|
oid, oid);
|
|
|
|
#endif
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/* drop log table */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2008-12-08 04:32:10 +00:00
|
|
|
SPI_OK_UTILITY,
|
2012-11-10 22:33:57 +00:00
|
|
|
"DROP TABLE IF EXISTS repack.log_%u CASCADE",
|
2008-12-08 04:32:10 +00:00
|
|
|
oid);
|
|
|
|
|
|
|
|
/* drop temp table */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2008-12-08 04:32:10 +00:00
|
|
|
SPI_OK_UTILITY,
|
2012-11-10 22:33:57 +00:00
|
|
|
"DROP TABLE IF EXISTS repack.table_%u CASCADE",
|
2008-12-08 04:32:10 +00:00
|
|
|
oid);
|
|
|
|
|
|
|
|
/* drop type for log table */
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2008-12-08 04:32:10 +00:00
|
|
|
SPI_OK_UTILITY,
|
2012-11-10 22:33:57 +00:00
|
|
|
"DROP TYPE IF EXISTS repack.pk_%u CASCADE",
|
2008-12-08 04:32:10 +00:00
|
|
|
oid);
|
|
|
|
|
|
|
|
SPI_finish();
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
2009-05-25 07:06:38 +00:00
|
|
|
Datum
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_disable_autovacuum(PG_FUNCTION_ARGS)
|
2009-05-25 07:06:38 +00:00
|
|
|
{
|
|
|
|
Oid oid = PG_GETARG_OID(0);
|
|
|
|
|
|
|
|
/* connect to SPI manager */
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init();
|
2009-05-25 07:06:38 +00:00
|
|
|
|
|
|
|
#if PG_VERSION_NUM >= 80400
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2009-05-25 07:06:38 +00:00
|
|
|
SPI_OK_UTILITY,
|
|
|
|
"ALTER TABLE %s SET (autovacuum_enabled = off)",
|
|
|
|
get_relation_name(oid));
|
|
|
|
#else
|
2009-07-02 09:50:58 +00:00
|
|
|
execute_with_format(
|
2009-05-25 07:06:38 +00:00
|
|
|
SPI_OK_INSERT,
|
|
|
|
"INSERT INTO pg_catalog.pg_autovacuum VALUES (%u, false, -1, -1, -1, -1, -1, -1, -1, -1)",
|
|
|
|
oid);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
SPI_finish();
|
|
|
|
|
|
|
|
PG_RETURN_VOID();
|
|
|
|
}
|
|
|
|
|
2008-12-08 04:32:10 +00:00
|
|
|
/* init SPI */
|
|
|
|
static void
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_init(void)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
int ret = SPI_connect();
|
|
|
|
if (ret != SPI_OK_CONNECT)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "pg_repack: SPI_connect returned %d", ret);
|
2008-12-08 04:32:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* prepare plan */
|
|
|
|
static SPIPlanPtr
|
2012-11-10 22:33:57 +00:00
|
|
|
repack_prepare(const char *src, int nargs, Oid *argtypes)
|
2008-12-08 04:32:10 +00:00
|
|
|
{
|
|
|
|
SPIPlanPtr plan = SPI_prepare(src, nargs, argtypes);
|
|
|
|
if (plan == NULL)
|
2012-11-10 22:33:57 +00:00
|
|
|
elog(ERROR, "pg_repack: repack_prepare failed (code=%d, query=%s)", SPI_result, src);
|
2008-12-08 04:32:10 +00:00
|
|
|
return plan;
|
|
|
|
}
|
|
|
|
|
2009-01-19 04:28:21 +00:00
|
|
|
static const char *
|
|
|
|
get_quoted_relname(Oid oid)
|
|
|
|
{
|
2012-11-11 15:24:38 -05:00
|
|
|
const char *relname = get_rel_name(oid);
|
|
|
|
return (relname ? quote_identifier(relname) : NULL);
|
2009-01-19 04:28:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
|
|
get_quoted_nspname(Oid oid)
|
|
|
|
{
|
2012-11-11 15:24:38 -05:00
|
|
|
const char *nspname = get_namespace_name(get_rel_namespace(oid));
|
|
|
|
return (nspname ? quote_identifier(nspname) : NULL);
|
2009-01-19 04:28:21 +00:00
|
|
|
}
|
2009-01-21 08:09:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This is a copy of swap_relation_files in cluster.c, but it also swaps
|
|
|
|
* relfrozenxid.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
swap_heap_or_index_files(Oid r1, Oid r2)
|
|
|
|
{
|
|
|
|
Relation relRelation;
|
|
|
|
HeapTuple reltup1,
|
|
|
|
reltup2;
|
|
|
|
Form_pg_class relform1,
|
|
|
|
relform2;
|
|
|
|
Oid swaptemp;
|
|
|
|
CatalogIndexState indstate;
|
|
|
|
|
|
|
|
/* We need writable copies of both pg_class tuples. */
|
|
|
|
relRelation = heap_open(RelationRelationId, RowExclusiveLock);
|
|
|
|
|
|
|
|
reltup1 = SearchSysCacheCopy(RELOID,
|
|
|
|
ObjectIdGetDatum(r1),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(reltup1))
|
|
|
|
elog(ERROR, "cache lookup failed for relation %u", r1);
|
|
|
|
relform1 = (Form_pg_class) GETSTRUCT(reltup1);
|
|
|
|
|
|
|
|
reltup2 = SearchSysCacheCopy(RELOID,
|
|
|
|
ObjectIdGetDatum(r2),
|
|
|
|
0, 0, 0);
|
|
|
|
if (!HeapTupleIsValid(reltup2))
|
|
|
|
elog(ERROR, "cache lookup failed for relation %u", r2);
|
|
|
|
relform2 = (Form_pg_class) GETSTRUCT(reltup2);
|
|
|
|
|
|
|
|
Assert(relform1->relkind == relform2->relkind);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Actually swap the fields in the two tuples
|
|
|
|
*/
|
|
|
|
swaptemp = relform1->relfilenode;
|
|
|
|
relform1->relfilenode = relform2->relfilenode;
|
|
|
|
relform2->relfilenode = swaptemp;
|
|
|
|
|
|
|
|
swaptemp = relform1->reltablespace;
|
|
|
|
relform1->reltablespace = relform2->reltablespace;
|
|
|
|
relform2->reltablespace = swaptemp;
|
|
|
|
|
|
|
|
swaptemp = relform1->reltoastrelid;
|
|
|
|
relform1->reltoastrelid = relform2->reltoastrelid;
|
|
|
|
relform2->reltoastrelid = swaptemp;
|
|
|
|
|
|
|
|
/* set rel1's frozen Xid to larger one */
|
|
|
|
if (TransactionIdIsNormal(relform1->relfrozenxid))
|
|
|
|
{
|
|
|
|
if (TransactionIdFollows(relform1->relfrozenxid,
|
|
|
|
relform2->relfrozenxid))
|
|
|
|
relform1->relfrozenxid = relform2->relfrozenxid;
|
|
|
|
else
|
|
|
|
relform2->relfrozenxid = relform1->relfrozenxid;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* swap size statistics too, since new rel has freshly-updated stats */
|
|
|
|
{
|
2012-09-20 03:48:53 +00:00
|
|
|
#if PG_VERSION_NUM >= 90300
|
|
|
|
int32 swap_pages;
|
|
|
|
#else
|
2009-01-21 08:09:22 +00:00
|
|
|
int4 swap_pages;
|
2012-09-20 03:48:53 +00:00
|
|
|
#endif
|
2009-01-21 08:09:22 +00:00
|
|
|
float4 swap_tuples;
|
|
|
|
|
|
|
|
swap_pages = relform1->relpages;
|
|
|
|
relform1->relpages = relform2->relpages;
|
|
|
|
relform2->relpages = swap_pages;
|
|
|
|
|
|
|
|
swap_tuples = relform1->reltuples;
|
|
|
|
relform1->reltuples = relform2->reltuples;
|
|
|
|
relform2->reltuples = swap_tuples;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Update the tuples in pg_class */
|
|
|
|
simple_heap_update(relRelation, &reltup1->t_self, reltup1);
|
|
|
|
simple_heap_update(relRelation, &reltup2->t_self, reltup2);
|
|
|
|
|
|
|
|
/* Keep system catalogs current */
|
|
|
|
indstate = CatalogOpenIndexes(relRelation);
|
|
|
|
CatalogIndexInsert(indstate, reltup1);
|
|
|
|
CatalogIndexInsert(indstate, reltup2);
|
|
|
|
CatalogCloseIndexes(indstate);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If we have toast tables associated with the relations being swapped,
|
|
|
|
* change their dependency links to re-associate them with their new
|
|
|
|
* owning relations. Otherwise the wrong one will get dropped ...
|
|
|
|
*
|
|
|
|
* NOTE: it is possible that only one table has a toast table; this can
|
|
|
|
* happen in CLUSTER if there were dropped columns in the old table, and
|
|
|
|
* in ALTER TABLE when adding or changing type of columns.
|
|
|
|
*
|
|
|
|
* NOTE: at present, a TOAST table's only dependency is the one on its
|
|
|
|
* owning table. If more are ever created, we'd need to use something
|
|
|
|
* more selective than deleteDependencyRecordsFor() to get rid of only the
|
|
|
|
* link we want.
|
|
|
|
*/
|
|
|
|
if (relform1->reltoastrelid || relform2->reltoastrelid)
|
|
|
|
{
|
|
|
|
ObjectAddress baseobject,
|
|
|
|
toastobject;
|
|
|
|
long count;
|
|
|
|
|
|
|
|
/* Delete old dependencies */
|
|
|
|
if (relform1->reltoastrelid)
|
|
|
|
{
|
|
|
|
count = deleteDependencyRecordsFor(RelationRelationId,
|
2011-03-03 01:21:40 +00:00
|
|
|
relform1->reltoastrelid,
|
|
|
|
false);
|
2009-01-21 08:09:22 +00:00
|
|
|
if (count != 1)
|
|
|
|
elog(ERROR, "expected one dependency record for TOAST table, found %ld",
|
|
|
|
count);
|
|
|
|
}
|
|
|
|
if (relform2->reltoastrelid)
|
|
|
|
{
|
|
|
|
count = deleteDependencyRecordsFor(RelationRelationId,
|
2011-03-03 01:21:40 +00:00
|
|
|
relform2->reltoastrelid,
|
|
|
|
false);
|
2009-01-21 08:09:22 +00:00
|
|
|
if (count != 1)
|
|
|
|
elog(ERROR, "expected one dependency record for TOAST table, found %ld",
|
|
|
|
count);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Register new dependencies */
|
|
|
|
baseobject.classId = RelationRelationId;
|
|
|
|
baseobject.objectSubId = 0;
|
|
|
|
toastobject.classId = RelationRelationId;
|
|
|
|
toastobject.objectSubId = 0;
|
|
|
|
|
|
|
|
if (relform1->reltoastrelid)
|
|
|
|
{
|
|
|
|
baseobject.objectId = r1;
|
|
|
|
toastobject.objectId = relform1->reltoastrelid;
|
|
|
|
recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (relform2->reltoastrelid)
|
|
|
|
{
|
|
|
|
baseobject.objectId = r2;
|
|
|
|
toastobject.objectId = relform2->reltoastrelid;
|
|
|
|
recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Blow away the old relcache entries now. We need this kluge because
|
|
|
|
* relcache.c keeps a link to the smgr relation for the physical file, and
|
|
|
|
* that will be out of date as soon as we do CommandCounterIncrement.
|
|
|
|
* Whichever of the rels is the second to be cleared during cache
|
|
|
|
* invalidation will have a dangling reference to an already-deleted smgr
|
|
|
|
* relation. Rather than trying to avoid this by ordering operations just
|
|
|
|
* so, it's easiest to not have the relcache entries there at all.
|
|
|
|
* (Fortunately, since one of the entries is local in our transaction,
|
|
|
|
* it's sufficient to clear out our own relcache this way; the problem
|
|
|
|
* cannot arise for other backends when they see our update on the
|
|
|
|
* non-local relation.)
|
|
|
|
*/
|
|
|
|
RelationForgetRelation(r1);
|
|
|
|
RelationForgetRelation(r2);
|
|
|
|
|
|
|
|
/* Clean up. */
|
|
|
|
heap_freetuple(reltup1);
|
|
|
|
heap_freetuple(reltup2);
|
|
|
|
|
|
|
|
heap_close(relRelation, RowExclusiveLock);
|
|
|
|
}
|
|
|
|
|
|
|
|
#if PG_VERSION_NUM < 80400
|
2009-01-23 02:33:11 +00:00
|
|
|
|
2011-01-25 06:41:12 +00:00
|
|
|
/* XXX: You might need to add PGDLLIMPORT into your miscadmin.h. */
|
2009-01-23 02:33:11 +00:00
|
|
|
extern PGDLLIMPORT bool allowSystemTableMods;
|
|
|
|
|
2009-01-21 08:09:22 +00:00
|
|
|
static void
|
|
|
|
RenameRelationInternal(Oid myrelid, const char *newrelname, Oid namespaceId)
|
|
|
|
{
|
2009-01-23 02:33:11 +00:00
|
|
|
bool save_allowSystemTableMods = allowSystemTableMods;
|
|
|
|
|
|
|
|
allowSystemTableMods = true;
|
|
|
|
PG_TRY();
|
|
|
|
{
|
2012-10-17 08:00:47 -07:00
|
|
|
renamerel(myrelid, newrelname, OBJECT_TABLE);
|
2009-01-23 02:33:11 +00:00
|
|
|
allowSystemTableMods = save_allowSystemTableMods;
|
|
|
|
}
|
|
|
|
PG_CATCH();
|
|
|
|
{
|
|
|
|
allowSystemTableMods = save_allowSystemTableMods;
|
|
|
|
PG_RE_THROW();
|
|
|
|
}
|
|
|
|
PG_END_TRY();
|
2009-01-21 08:09:22 +00:00
|
|
|
}
|
|
|
|
#endif
|