Initial revision

This commit is contained in:
Takahiro Itagaki 2008-12-08 04:32:10 +00:00
commit 8af8be23ac
12 changed files with 2638 additions and 0 deletions

26
Makefile Executable file
View File

@ -0,0 +1,26 @@
#
# pg_reorg: Makefile
#
# Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
.PHONY: all install clean
all:
make -C bin
make -C lib
install:
make -C bin install
make -C lib install
clean:
make -C bin clean
make -C lib clean
debug:
make -C bin DEBUG_REORG=enable
make -C lib DEBUG_REORG=enable
uninstall:
make -C bin uninstall
make -C lib uninstall

26
bin/Makefile Executable file
View File

@ -0,0 +1,26 @@
#
# pg_reorg: bin/Makefile
#
# Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
SRCS = \
pg_reorg.c
OBJS = $(SRCS:.c=.o) $(top_builddir)/src/bin/scripts/common.o
PROGRAM = pg_reorg
ifdef DEBUG_REORG
PG_CPPFLAGS = -I$(libpq_srcdir) -I$(top_builddir)/src/bin/scripts -DDEBUG_REORG
else
PG_CPPFLAGS = -I$(libpq_srcdir) -I$(top_builddir)/src/bin/scripts
endif
PG_LIBS = $(libpq)
ifdef USE_PGXS
PGXS := $(shell pg_config --pgxs)
include $(PGXS)
else
subdir = contrib/pg_reorg
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

946
bin/pg_reorg.c Executable file
View File

@ -0,0 +1,946 @@
/*
* pg_reorg.c: bin/pg_reorg.c
*
* Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*/
/**
* @brief Client Modules
*/
#include "postgres_fe.h"
#include "common.h"
#include "libpq/pqsignal.h"
#include <unistd.h>
#include <signal.h>
#define REORG_VERSION "1.0.2"
#define REORG_URL "http://reorg.projects.postgresql.org/"
#define REORG_EMAIL "reorg-general@lists.pgfoundry.org"
#define APPLY_COUNT 1000
#if PG_VERSION_NUM >= 80300
#define SQL_XID_SNAPSHOT \
"SELECT reorg.array_accum(virtualtransaction) FROM pg_locks"\
" WHERE locktype = 'virtualxid' AND pid <> pg_backend_pid()"
#define SQL_XID_ALIVE \
"SELECT 1 FROM pg_locks WHERE locktype = 'virtualxid'"\
" AND pid <> pg_backend_pid() AND virtualtransaction = ANY($1) LIMIT 1"
#else
#define SQL_XID_SNAPSHOT \
"SELECT reorg.array_accum(transactionid) FROM pg_locks"\
" WHERE locktype = 'transactionid' AND pid <> pg_backend_pid()"
#define SQL_XID_ALIVE \
"SELECT 1 FROM pg_locks WHERE locktype = 'transactionid'"\
" AND pid <> pg_backend_pid() AND transactionid = ANY($1) LIMIT 1"
#endif
/*
* per-table information
*/
typedef struct reorg_table
{
const char *target_name; /* target: relname */
Oid target_oid; /* target: OID */
Oid target_toast; /* target: toast OID */
Oid target_tidx; /* target: toast index OID */
Oid pkid; /* target: PK OID */
Oid ckid; /* target: CK OID */
const char *create_pktype; /* CREATE TYPE pk */
const char *create_log; /* CREATE TABLE log */
const char *create_trigger; /* CREATE TRIGGER z_reorg_trigger */
const char *create_table; /* CREATE TABLE table AS SELECT */
const char *delete_log; /* DELETE FROM log */
const char *lock_table; /* LOCK TABLE table */
const char *sql_peek; /* SQL used in flush */
const char *sql_insert; /* SQL used in flush */
const char *sql_delete; /* SQL used in flush */
const char *sql_update; /* SQL used in flush */
const char *sql_pop; /* SQL used in flush */
} reorg_table;
/*
* per-index information
*/
typedef struct reorg_index
{
Oid target_oid; /* target: OID */
const char *create_index; /* CREATE INDEX */
} reorg_index;
static void reorg_all_databases(const char *orderby);
static bool reorg_one_database(const char *orderby, const char *table);
static void reorg_one_table(const reorg_table *table, const char* orderby);
static void reconnect(void);
static void disconnect(void);
static PGresult *execute_nothrow(const char *query, int nParams, const char **params);
static PGresult *execute(const char *query, int nParams, const char **params);
static void command(const char *query, int nParams, const char **params);
static void cleanup(void);
static void exit_with_cleanup(int exitcode);
static void reorg_setup_cancel_handler(void);
static void reorg_command_begin(PGconn *conn);
static void reorg_command_end(void);
static void PrintHelp(const char *progname);
static void PrintVersion(void);
static char *getstr(PGresult *res, int row, int col);
static Oid getoid(PGresult *res, int row, int col);
static const char *progname = NULL;
static bool echo = false;
static bool verbose = false;
static bool quiet = false;
/* connectin parameters */
static const char *dbname = NULL;
static char *host = NULL;
static char *port = NULL;
static char *username = NULL;
static bool password = false;
/*
* The table begin re-organized. If not null, we need to cleanup temp
* objects before the program exits.
*/
static const reorg_table *current_table = NULL;
/* Current connection initizlied with coneection parameters. */
static PGconn *current_conn = NULL;
/* Interrupted by SIGINT (Ctrl+C) ? */
static bool interrupted = false;
/* Not null during executing some SQL commands. */
static PGcancel *volatile cancelConn = NULL;
#ifdef WIN32
static CRITICAL_SECTION cancelConnLock;
static unsigned int sleep(unsigned int seconds)
{
Sleep(seconds * 1000);
return 0;
}
#endif
/* buffer should have at least 11 bytes */
static char *
utoa(unsigned int value, char *buffer)
{
sprintf(buffer, "%u", value);
return buffer;
}
/* called by atexit */
static void
warn_if_unclean(void)
{
if (current_table)
fprintf(stderr, _("!!!FATAL ERROR!!! Please refer to a manual.\n\n"));
}
int
main(int argc, char *argv[])
{
static struct option long_options[] = {
{"host", required_argument, NULL, 'h'},
{"port", required_argument, NULL, 'p'},
{"username", required_argument, NULL, 'U'},
{"password", no_argument, NULL, 'W'},
{"echo", no_argument, NULL, 'e'},
{"quiet", no_argument, NULL, 'q'},
{"verbose", no_argument, NULL, 'v'},
{"dbname", required_argument, NULL, 'd'},
{"all", no_argument, NULL, 'a'},
{"table", required_argument, NULL, 't'},
{"no-order", no_argument, NULL, 'n'},
{"order-by", required_argument, NULL, 'o'},
{NULL, 0, NULL, 0}
};
int optindex;
int c;
bool alldb = false;
const char *table = NULL;
const char *orderby = NULL;
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], "pgscripts");
/*
* Help message and version are handled at first.
*/
if (argc > 1)
{
if (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") == 0)
{
PrintHelp(progname);
return 0;
}
if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)
{
PrintVersion();
return 0;
}
}
while ((c = getopt_long(argc, argv, "h:p:U:Weqvd:at:no:", long_options, &optindex)) != -1)
{
switch (c)
{
case 'h':
host = optarg;
break;
case 'p':
port = optarg;
break;
case 'U':
username = optarg;
break;
case 'W':
password = true;
break;
case 'e':
echo = true;
break;
case 'q':
quiet = true;
break;
case 'v':
verbose = true;
break;
case 'd':
dbname = optarg;
break;
case 'a':
alldb = true;
break;
case 't':
table = optarg;
break;
case 'n':
orderby = "";
break;
case 'o':
orderby = optarg;
break;
default:
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
}
}
switch (argc - optind)
{
case 0:
break;
case 1:
dbname = argv[optind];
break;
default:
fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"),
progname, argv[optind + 1]);
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
}
reorg_setup_cancel_handler();
atexit(warn_if_unclean);
if (alldb)
{
if (dbname)
{
fprintf(stderr, _("%s: cannot reorg all databases and a specific one at the same time\n"),
progname);
exit(1);
}
if (table)
{
fprintf(stderr, _("%s: cannot reorg a specific table in all databases\n"),
progname);
exit(1);
}
reorg_all_databases(orderby);
}
else
{
(void) (dbname ||
(dbname = getenv("PGDATABASE")) ||
(dbname = getenv("PGUSER")) ||
(dbname = get_user_name(progname)));
if (!reorg_one_database(orderby, table))
{
fprintf(stderr, _("ERROR: %s is not installed\n"), progname);
return 1;
}
}
return 0;
}
/*
* Call reorg_one_database for each database.
*/
static void
reorg_all_databases(const char *orderby)
{
PGresult *result;
int i;
dbname = "postgres";
reconnect();
result = execute("SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;", 0, NULL);
disconnect();
for (i = 0; i < PQntuples(result); i++)
{
bool ret;
dbname = PQgetvalue(result, i, 0);
if (!quiet)
{
printf(_("%s: reorg database \"%s\""), progname, dbname);
fflush(stdout);
}
ret = reorg_one_database(orderby, NULL);
if (!quiet)
{
if (ret)
printf("\n");
else
printf(_(" ... skipped\n"));
fflush(stdout);
}
}
PQclear(result);
}
/* result is not copied */
static char *
getstr(PGresult *res, int row, int col)
{
if (PQgetisnull(res, row, col))
return NULL;
else
return PQgetvalue(res, row, col);
}
static Oid
getoid(PGresult *res, int row, int col)
{
if (PQgetisnull(res, row, col))
return InvalidOid;
else
return (Oid)strtoul(PQgetvalue(res, row, col), NULL, 10);
}
/*
* Call reorg_one_table for the target table or each table in a database.
*/
static bool
reorg_one_database(const char *orderby, const char *table)
{
bool ret = true;
PGresult *res;
int i;
int num;
PQExpBufferData sql;
initPQExpBuffer(&sql);
reconnect();
/* Restrict search_path to system catalog. */
command("SET search_path = pg_catalog, pg_temp", 0, NULL);
/* To avoid annoying "create implicit ..." messages. */
command("SET client_min_messages = warning", 0, NULL);
/* acquire target tables */
appendPQExpBufferStr(&sql, "SELECT * FROM reorg.tables WHERE ");
if (table)
{
appendPQExpBufferStr(&sql, "relid = $1::regclass");
res = execute_nothrow(sql.data, 1, &table);
}
else
{
appendPQExpBufferStr(&sql, "pkid IS NOT NULL");
if (!orderby)
appendPQExpBufferStr(&sql, " AND ckid IS NOT NULL");
res = execute_nothrow(sql.data, 0, NULL);
}
if (PQresultStatus(res) != PGRES_TUPLES_OK)
{
const char *state = PQresultErrorField(res, PG_DIAG_SQLSTATE);
if (state && strcmp(state, "3F000") == 0)
{
/* Schema reorg does not exist. Skip the database. */
ret = false;
goto cleanup;
}
else
{
/* exit otherwise */
printf("%s", PQerrorMessage(current_conn));
PQclear(res);
exit_with_cleanup(1);
}
}
num = PQntuples(res);
for (i = 0; i < num; i++)
{
reorg_table table;
const char *create_table;
const char *ckey;
int c = 0;
table.target_name = getstr(res, i, c++);
table.target_oid = getoid(res, i, c++);
table.target_toast = getoid(res, i, c++);
table.target_tidx = getoid(res, i, c++);
table.pkid = getoid(res, i, c++);
table.ckid = getoid(res, i, c++);
if (table.pkid == 0)
{
fprintf(stderr, _("ERROR: relation \"%s\" has no primary key\n"), table.target_name);
exit_with_cleanup(1);
}
table.create_pktype = getstr(res, i, c++);
table.create_log = getstr(res, i, c++);
table.create_trigger = getstr(res, i, c++);
create_table = getstr(res, i, c++);
table.delete_log = getstr(res, i, c++);
table.lock_table = getstr(res, i, c++);
ckey = getstr(res, i, c++);
resetPQExpBuffer(&sql);
if (!orderby)
{
/* CLUSTER mode */
if (ckey == NULL)
{
fprintf(stderr, _("ERROR: relation \"%s\" has no cluster key\n"), table.target_name);
exit_with_cleanup(1);
}
appendPQExpBuffer(&sql, "%s ORDER BY %s", create_table, ckey);
table.create_table = sql.data;
}
else if (!orderby[0])
{
/* VACUUM FULL mode */
table.create_table = create_table;
}
else
{
/* User specified ORDER BY */
appendPQExpBuffer(&sql, "%s ORDER BY %s", create_table, orderby);
table.create_table = sql.data;
}
table.sql_peek = getstr(res, i, c++);
table.sql_insert = getstr(res, i, c++);
table.sql_delete = getstr(res, i, c++);
table.sql_update = getstr(res, i, c++);
table.sql_pop = getstr(res, i, c++);
reorg_one_table(&table, orderby);
}
cleanup:
PQclear(res);
disconnect();
termPQExpBuffer(&sql);
return ret;
}
static int
apply_log(const reorg_table *table, int count)
{
int result;
PGresult *res;
const char *params[6];
char buffer[12];
params[0] = table->sql_peek;
params[1] = table->sql_insert;
params[2] = table->sql_delete;
params[3] = table->sql_update;
params[4] = table->sql_pop;
params[5] = utoa(count, buffer);
res = execute("SELECT reorg.reorg_apply($1, $2, $3, $4, $5, $6)",
6, params);
result = atoi(PQgetvalue(res, 0, 0));
PQclear(res);
return result;
}
/*
* Re-organize one table.
*/
static void
reorg_one_table(const reorg_table *table, const char *orderby)
{
PGresult *res;
const char *params[1];
int num;
int i;
char *vxid;
char buffer[12];
if (verbose)
{
fprintf(stderr, "---- reorg_one_table ----\n");
fprintf(stderr, "target_name : %s\n", table->target_name);
fprintf(stderr, "target_oid : %u\n", table->target_oid);
fprintf(stderr, "target_toast : %u\n", table->target_toast);
fprintf(stderr, "target_tidx : %u\n", table->target_tidx);
fprintf(stderr, "pkid : %u\n", table->pkid);
fprintf(stderr, "ckid : %u\n", table->ckid);
fprintf(stderr, "create_pktype : %s\n", table->create_pktype);
fprintf(stderr, "create_log : %s\n", table->create_log);
fprintf(stderr, "create_trigger : %s\n", table->create_trigger);
fprintf(stderr, "create_table : %s\n", table->create_table);
fprintf(stderr, "delete_log : %s\n", table->delete_log);
fprintf(stderr, "lock_table : %s\n", table->lock_table);
fprintf(stderr, "sql_peek : %s\n", table->sql_peek);
fprintf(stderr, "sql_insert : %s\n", table->sql_insert);
fprintf(stderr, "sql_delete : %s\n", table->sql_delete);
fprintf(stderr, "sql_update : %s\n", table->sql_update);
fprintf(stderr, "sql_pop : %s\n", table->sql_pop);
}
/*
* 1. Setup workspaces and a trigger.
*/
if (verbose)
fprintf(stderr, "---- setup ----\n");
command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL);
/*
* Check z_reorg_trigger is the trigger executed at last so that
* other before triggers cannot modify triggered tuples.
*/
params[0] = utoa(table->target_oid, buffer);
res = execute(
"SELECT 1 FROM pg_trigger"
" WHERE tgrelid = $1 AND tgname >= 'z_reorg_trigger' LIMIT 1",
1, params);
if (PQntuples(res) > 0)
{
fprintf(stderr, _("%s: trigger conflicted for %s\n"),
progname, table->target_name);
exit_with_cleanup(1);
}
command(table->create_pktype, 0, NULL);
command(table->create_log, 0, NULL);
command(table->create_trigger, 0, NULL);
command("COMMIT", 0, NULL);
/*
* Register the table to be dropped on error. We use pktype as
* an advisory lock. The registration should be done after
* the first command is succeeded.
*/
current_table = table;
/*
* 2. Copy tuples into temp table.
*/
if (verbose)
fprintf(stderr, "---- copy tuples ----\n");
command("BEGIN ISOLATION LEVEL SERIALIZABLE", 0, NULL);
if (orderby && !orderby[0])
command("SET LOCAL synchronize_seqscans = off", 0, NULL);
res = execute(SQL_XID_SNAPSHOT, 0, NULL);
vxid = strdup(PQgetvalue(res, 0, 0));
PQclear(res);
command(table->delete_log, 0, NULL);
command(table->create_table, 0, NULL);
command("COMMIT", 0, NULL);
/*
* 3. Create indexes on temp table.
*/
if (verbose)
fprintf(stderr, "---- create indexes ----\n");
params[0] = utoa(table->target_oid, buffer);
res = execute("SELECT indexrelid,"
" reorg.reorg_indexdef(indexrelid, indrelid)"
" FROM pg_index WHERE indrelid = $1", 1, params);
num = PQntuples(res);
for (i = 0; i < num; i++)
{
reorg_index index;
int c = 0;
index.target_oid = getoid(res, i, c++);
index.create_index = getstr(res, i, c++);
if (verbose)
{
fprintf(stderr, "[%d]\n", i);
fprintf(stderr, "target_oid : %u\n", index.target_oid);
fprintf(stderr, "create_index : %s\n", index.create_index);
}
/*
* NOTE: If we want to create multiple indexes in parallel,
* we need to call create_index in multiple connections.
*/
command(index.create_index, 0, NULL);
}
PQclear(res);
/*
* 4. Apply log to temp table until no tuples left in the log
* and all of old transactions are finished.
*/
for (;;)
{
num = apply_log(table, APPLY_COUNT);
if (num > 0)
continue; /* there might be still some tuples, repeat. */
/* old transactions still alive ? */
params[0] = vxid;
res = execute(SQL_XID_ALIVE, 1, params);
num = PQntuples(res);
PQclear(res);
if (num > 0)
{
sleep(1);
continue; /* wait for old transactions */
}
/* ok, go next step. */
break;
}
/*
* 5. Cleanup.
*/
if (verbose)
fprintf(stderr, "---- cleanup ----\n");
command("BEGIN ISOLATION LEVEL READ COMMITTED", 0, NULL);
command(table->lock_table, 0, NULL);
apply_log(table, 0);
params[0] = utoa(table->target_oid, buffer);
command("SELECT reorg.reorg_swap($1)", 1, params);
command("SELECT reorg.reorg_drop($1)", 1, params);
command("COMMIT", 0, NULL);
current_table = NULL;
free(vxid);
}
static void
cleanup(void)
{
char buffer[12];
const char *params[1];
if (!current_table)
return;
params[0] = utoa(current_table->target_oid, buffer);
execute("SELECT reorg.reorg_drop($1)", 1, params);
current_table = NULL;
}
static void
reconnect(void)
{
disconnect();
current_conn = connectDatabase(dbname, host, port, username, password, progname);
}
static void
disconnect(void)
{
if (current_conn)
{
PQfinish(current_conn);
current_conn = NULL;
}
}
static void
exit_with_cleanup(int exitcode)
{
if (current_table)
{
/* Rollback current transaction */
if (current_conn)
{
PGresult *res;
res = PQexec(current_conn, "ROLLBACK");
if (PQresultStatus(res) != PGRES_COMMAND_OK)
exit(1); // fatal error
PQclear(res);
}
/* Try reconnection if not available. */
if (PQstatus(current_conn) != CONNECTION_OK)
reconnect();
cleanup();
}
disconnect();
exit(exitcode);
}
static PGresult *
execute_nothrow(const char *query, int nParams, const char **params)
{
PGresult *res;
if (echo)
fprintf(stderr, _("%s: executing %s\n"), progname, query);
#ifdef DEBUG_REORG
fprintf(stderr, "debug: suspend in execute. (sql='%s')\npush enter key: ", query);
fgetc(stdin);
#endif
reorg_command_begin(current_conn);
if (nParams == 0)
res = PQexec(current_conn, query);
else
res = PQexecParams(current_conn, query, nParams, NULL, params, NULL, NULL, 0);
reorg_command_end();
return res;
}
/*
* execute - Execute a SQL and discard the result, or exit() if failed.
*/
static PGresult *
execute(const char *query, int nParams, const char **params)
{
if (interrupted)
{
interrupted = false;
fprintf(stderr, _("%s: interrupted\n"), progname);
}
else
{
PGresult *res = execute_nothrow(query, nParams, params);
if (PQresultStatus(res) == PGRES_TUPLES_OK ||
PQresultStatus(res) == PGRES_COMMAND_OK)
return res;
fprintf(stderr, _("%s: query failed: %s"),
progname, PQerrorMessage(current_conn));
fprintf(stderr, _("%s: query was: %s\n"),
progname, query);
PQclear(res);
}
exit_with_cleanup(1);
return NULL; /* keep compiler quiet */
}
/*
* command - Execute a SQL and discard the result, or exit() if failed.
*/
static void
command(const char *query, int nParams, const char **params)
{
PGresult *res = execute(query, nParams, params);
PQclear(res);
}
static void
PrintHelp(const char *progname)
{
printf(_("%s re-organizes a PostgreSQL database.\n\n"), progname);
printf(_("Usage:\n"));
printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
printf(_("\nOptions:\n"));
printf(_(" -a, --all reorg all databases\n"));
printf(_(" -d, --dbname=DBNAME database to reorg\n"));
printf(_(" -t, --table=TABLE reorg specific table only\n"));
printf(_(" -n, --no-order do vacuum full instead of cluster\n"));
printf(_(" -o, --order-by=columns order by columns instead of cluster keys\n"));
printf(_(" -e, --echo show the commands being sent to the server\n"));
printf(_(" -q, --quiet don't write any messages\n"));
printf(_(" -v, --verbose display detailed information during processing\n"));
printf(_(" --help show this help, then exit\n"));
printf(_(" --version output version information, then exit\n"));
printf(_("\nConnection options:\n"));
printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
printf(_(" -p, --port=PORT database server port\n"));
printf(_(" -U, --username=USERNAME user name to connect as\n"));
printf(_(" -W, --password force password prompt\n"));
#ifdef REORG_URL
printf(_("\nRead the website for details. <" REORG_URL ">\n"));
#endif
#ifdef REORG_EMAIL
printf(_("\nReport bugs to <" REORG_EMAIL ">.\n"));
#endif
}
static void
PrintVersion(void)
{
fprintf(stderr, "pg_reorg " REORG_VERSION "\n");
return;
}
/*
* reorg_command_begin
*
* Set cancelConn to point to the current database connection.
*/
static void
reorg_command_begin(PGconn *conn)
{
PGcancel *oldCancelConn;
#ifdef WIN32
EnterCriticalSection(&cancelConnLock);
#endif
/* Free the old one if we have one */
oldCancelConn = cancelConn;
/* be sure handle_sigint doesn't use pointer while freeing */
cancelConn = NULL;
if (oldCancelConn != NULL)
PQfreeCancel(oldCancelConn);
cancelConn = PQgetCancel(conn);
#ifdef WIN32
LeaveCriticalSection(&cancelConnLock);
#endif
}
/*
* reorg_command_end
*
* Free the current cancel connection, if any, and set to NULL.
*/
static void
reorg_command_end(void)
{
PGcancel *oldCancelConn;
#ifdef WIN32
EnterCriticalSection(&cancelConnLock);
#endif
oldCancelConn = cancelConn;
/* be sure handle_sigint doesn't use pointer while freeing */
cancelConn = NULL;
if (oldCancelConn != NULL)
PQfreeCancel(oldCancelConn);
#ifdef WIN32
LeaveCriticalSection(&cancelConnLock);
#endif
}
/*
* Handle interrupt signals by cancelling the current command.
*/
static void
reorg_cancel(void)
{
int save_errno = errno;
char errbuf[256];
/* Set interruped flag */
interrupted = true;
/* Send QueryCancel if we are processing a database query */
if (cancelConn != NULL && PQcancel(cancelConn, errbuf, sizeof(errbuf)))
fprintf(stderr, _("Cancel request sent\n"));
errno = save_errno; /* just in case the write changed it */
}
#ifndef WIN32
static void
handle_sigint(SIGNAL_ARGS)
{
reorg_cancel();
}
static void
reorg_setup_cancel_handler(void)
{
pqsignal(SIGINT, handle_sigint);
}
#else /* WIN32 */
/*
* Console control handler for Win32. Note that the control handler will
* execute on a *different thread* than the main one, so we need to do
* proper locking around those structures.
*/
static BOOL WINAPI
consoleHandler(DWORD dwCtrlType)
{
if (dwCtrlType == CTRL_C_EVENT ||
dwCtrlType == CTRL_BREAK_EVENT)
{
EnterCriticalSection(&cancelConnLock);
reorg_cancel();
LeaveCriticalSection(&cancelConnLock);
return TRUE;
}
else
/* Return FALSE for any signals not being handled */
return FALSE;
}
static void
reorg_setup_cancel_handler(void)
{
InitializeCriticalSection(&cancelConnLock);
SetConsoleCtrlHandler(consoleHandler, TRUE);
}
#endif /* WIN32 */

53
doc/index-ja.html Executable file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<link rel="icon" type="image/png" href="http://pgfoundry.org/images/elephant-icon.png" />
<link rel="stylesheet" type="text/css" href="style.css" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>pg_reorg: Project Home Page</title>
</head>
<body>
<center><img style="border: none; margin-left: auto; margin-right: auto; " src="http://pgfoundry.org/images/elephantSmall.png" height="75" width="75" />
<hr />
<h1>pg_reorg ホームページへようこそ</h1>
<hr />
</center>
<p>
pg_reorg は PostgreSQL のテーブルを再編成するシェルコマンドです。
共有ロックや排他ロックを取得しないため、再編成中であっても行の参照や更新を行うことができます。
このモジュールは CLUSTER や VACUUM FULL コマンドのより良い代替になります。
</p>
<p>この pg_reorg プロジェクトは <a href="http://www.postgresql.org">PostgreSQL</a> コミュニティによる <a href="http://pgfoundry.org">pgFoundry</a> の中の<a href="http://pgfoundry.org/projects/reorg">プロジェクト</a>です。</p>
<ul>
<li><a href="http://pgfoundry.org/frs/?group_id=1000411">ダウンロード</a> : ソースコードのほか、Windows 用バイナリもダウンロードできます。</li>
<li><a href="http://pgfoundry.org/tracker/?group_id=1000411">バグレポート</li></li>
<li><a href="http://pgfoundry.org/mail/?group_id=1000411">メーリングリスト</a> への参加</li>
</ul>
<div>
<a href="index.html">Here is an English page.</a>
</div>
<hr />
<p>
<a href="pg_reorg-ja.html">ドキュメントはこちら</a>
</p>
<hr />
<div align="right">
Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
</div>
<!-- PLEASE LEAVE "Powered By GForge" on your site -->
<br />
<center>
<a href="http://gforge.org/"><img src="http://gforge.org/images/pow-gforge.png"
alt="Powered By GForge Collaborative Development Environment" border="0" /></a>
</center>
</body>
</html>

53
doc/index.html Executable file
View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="icon" type="image/png" href="http://pgfoundry.org/images/elephant-icon.png" />
<link rel="stylesheet" type="text/css" href="style.css" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>pg_reorg: Project Home Page</title>
</head>
<body>
<center><img style="border: none; margin-left: auto; margin-right: auto; " src="http://pgfoundry.org/images/elephantSmall.png" height="75" width="75" />
<hr />
<h1>Welcome to the pg_reorg Project Home Page</h1>
<hr />
</center>
<p>
pg_reorg can re-organize tables on a postgres database without any locks so that you can retrieve or update rows in tables being reorganized.
The module is developed to be a better alternative of CLUSTER and VACUUM FULL.
</p>
<p>
The pg_reorg project is a <a href="http://www.postgresql.org">PostgreSQL</a> Community project that is a part of the <a href="http://pgfoundry.org">pgFoundry</a>.
</p>
<p>
The pgFoundry page for the project is at <a href="http://pgfoundry.org/projects/reorg">http://pgfoundry.org/projects/reorg</a>,
where you can find <a href="http://pgfoundry.org/frs/?group_id=1000411">downloads</a>, documentation, bug reports, mailing lists, and a whole lot more.
</p>
<div>
<a href="index-ja.html">日本語のページはこちら。</a>
</div>
<hr />
<p>
<a href="pg_reorg.html">Documentations here</a>.
</p>
<hr />
<div align="right">
Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
</div>
<!-- PLEASE LEAVE "Powered By GForge" on your site -->
<br />
<center>
<a href="http://gforge.org/"><img src="http://gforge.org/images/pow-gforge.png"
alt="Powered By GForge Collaborative Development Environment" border="0" /></a>
</center>
</body>
</html>

291
doc/pg_reorg-ja.html Executable file
View File

@ -0,0 +1,291 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>pg_reorg</title>
<link rel="home" title="pg_reorg " href="index.html">
<link rel="stylesheet" TYPE="text/css"href="style.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1><a name="pg_reorg"></a>pg_reorg</h1>
<div><a name="name"></a>
<h2>名前</h2>
pg_reorg -- PostgreSQLデータベース内のテーブルに対して、参照/更新処理をブロックせずに再編成を行います。
</div>
<div>
<a name="synopsis"></a>
<h2>概要</h2>
<p>
<tt>pg_reorg</tt> [<tt><i>connection-options</i></tt>...] [<tt><i>message-options</i></tt>...] [<tt><i>order-options</i></tt>...] [<tt><i>target-options</i></tt>...]
</p>
<p>指定できるオプションには4つのカテゴリがあります。
詳細は<a href="#options">オプション</a>を参照してください。</p>
<dl>
<dt><tt>connection-options</tt> : 接続パラメータ</dt>
<dd><tt>-h</tt> [<tt>--host</tt>] <tt><i>host</i></tt></dd>
<dd><tt>-p</tt> [<tt>--port</tt>] <tt><i>port</i></tt></dd>
<dd><tt>-U</tt> [<tt>--username</tt>] <tt><i>username</i></tt></dd>
<dd><tt>-W</tt> [<tt>--password</tt>]</dd>
<dt><tt>message-options</tt> : 出力メッセージ</dt>
<dd><tt>-e</tt> [<tt>--echo</tt>]</dd>
<dd><tt>-q</tt> [<tt>--quiet</tt>]</dd>
<dd><tt>-v</tt> [<tt>--verbose</tt>]</dd>
<dt><tt>order-options</tt> : 並び替えの基準</dt>
<dd><tt>-o</tt> [<tt>--order-by</tt>] <tt><i>columns [,...]</i></tt></dd>
<dd><tt>-n</tt> [<tt>--no-order</tt>]</dd>
<dt><tt>target-options</tt> : 処理対象</dt>
<dd><tt>-a</tt> [<tt>--all</tt>]</dd>
<dd><tt>-d</tt> [<tt>--dbname</tt>] <tt><i>dbname</i></tt></dd>
<dd><tt>-t</tt> [<tt>--table</tt>] <tt><i>table</i></tt></dd>
</dl>
</div>
<div><a name="description"></a>
<h2>説明</h2>
<p>pg_reorg は、PostgreSQLデータベース内のテーブルを再編成(行の並び替え)するユーティリティです。
<a href="http://www.postgresql.jp/document/current/html/app-clusterdb.html"><tt>clusterdb</tt></a> と異なり、参照/更新処理をブロックしません。
再編成の方式として、以下のいずれか1つを選択できます。</p>
<ul>
<li>オンライン CLUSTER (cluster index順に行を並び替える)</li>
<li>ユーザの指定した順に行を並び替える</li>
<li>オンライン VACUUM FULL (行の詰め合わせを行う)</li>
</ul>
<p>このユーティリティを使用するためには、以下のことに注意をしてください。</p>
<ul>
<li>このユーティリティは、スーパーユーザのみが実行することができます。</li>
<li>対象のテーブルはPRIMARY KEYを持っている必要があります。</li>
<li>pg_reorg 実行後のデータの状態は、統計情報に反映されていません。統計情報を最新化するため、pg_reorg 実行後に<tt><a href="http://www.postgresql.jp/document/current/html/sql-analyze.html">ANALYZE</a></tt>を実行してください。</li>
</ul>
</div>
<div><a name="examples"></a>
<h2></h2>
<p><tt>test</tt>というデータベースをオンライン CLUSTER するには、下記のコマンドを実行します。</p>
<PRE><SAMP>$ </SAMP><KBD>pg_reorg test</KBD></PRE>
<p><tt>test</tt>という名前のデータベースの<tt>foo</tt>という1つのテーブルに対してオンライン VACUUM FULL を行うには、下記のコマンドを実行します。</p>
<PRE><SAMP>$ </SAMP><KBD>pg_reorg --no-order --table foo -d test</KBD></PRE><p>
</p>
</div>
<div><a name="options"></a>
<h2>オプション</h2>
<p>pg_reorg では、下記の4種類のコマンドライン引数を指定できます。</p>
<div>
<dl>
<h3>connection-options</h3>
<p>PostgreSQLに接続するためのパラメータです。</p>
<div>
<dl>
<dt><tt>-h <tt><i>host</i></tt><br />
<tt>--host <tt><i>host</i></tt></dt>
<dd>サーバが稼働しているマシンのホスト名を指定します。ホスト名がスラッシュから始まる場合、Unixドメインソケット用のディレクトリとして使用されます。</dd>
<dt><tt>-p <tt><i>port</i></tt><br />
<tt>--port <tt><i>port</i></tt></dt>
<dd>サーバが接続を監視するTCPポートもしくはUnixドメインソケットファイルの拡張子を指定します。</dd>
<dt><tt>-U <tt><i>username</i></tt><br />
<tt>--username <tt><i>username</i></tt></dt>
<dd>接続するユーザ名を指定します。</dd>
<dt><tt>-W</tt><br /><tt>--password</tt></dt>
<dd>データベースに接続する前に、pg_reorg は強制的にパスワード入力を促します。</dd>
<dd>サーバがパスワード認証を要求する場合 pg_reorg は自動的にパスワード入力を促しますので、これが重要になることはありません。
しかし、pg_reorg は、サーバにパスワードが必要かどうかを判断するための接続試行を無駄に行います。
こうした余計な接続試行を防ぐために<tt>-W</tt>の入力が有意となる場合もあります。</dd>
</dl>
</div>
<h3>message-options</h3>
<p>
pg_reorg を実行した際に任意のメッセージを出力するためのパラメータです。
<tt>--quiet</tt>と他の2つのオプションを同時に指定した場合は、<tt>--quiet</tt>のオプションは無視されます。
</p>
<dl>
<dt><tt>-e</tt><br /><tt>--echo</tt></dt>
<dd>pg_reorg が生成し、サーバに送るコマンドをエコー表示します。</dd>
<dt><tt>-q</tt><br /><tt>--quiet</tt></dt>
<dd>進行メッセージを表示しません。</dd>
<dt><tt>-v</tt><br /><tt>--verbose</tt></dt>
<dd>処理中に詳細な情報を表示します。</dd>
</dl>
<h3>order-options</h3>
<p>pg_reorg を実行する際の並び替えの基準を指定するパラメータです。
何も指定されていない場合は、cluster index順にオンライン CLUSTER を行います。
この2つを同時に指定することはできません。
</p>
<dl>
<dt><tt>-n</tt><br /><tt>--no-order</tt></dt>
<dd>オンライン VACUUM FULL の処理を行います。</dd>
<dt><tt>-o</tt> <tt><i>columns [,...]</i></tt><br />
<tt>--order-by</tt> <tt><i>columns [,...]</i></tt></dt>
<dd>指定したカラムをキーにオンライン CLUSTER を行います。</dd>
</dl>
<h3>target-options</h3>
<p>
pg_reorg を実行する対象を指定するパラメータです。
<tt>--all</tt><tt>--dbname</tt>または<tt>--table</tt>を同時に指定することはできません。
</p>
<dl>
<dt><tt>-a</tt><br /><tt>--all</tt></dt>
<dd>対象となる全てのデータベースに対してオンライン CLUSTER、または、オンラインVACUUM FULLを行います。</dd>
<dt>
<tt>-d <tt><i>dbname</i></tt><br />
<tt>--dbname <tt><i>dbname</i></tt>
</dt>
<dd>オンライン CLUSTER、または、オンライン VACUUM FULL を行うデータベース名を指定します。
データベース名が指定されておらず、<tt>-a</tt>(または<tt>--all</tt>)も指定されていない場合、
データベース名は<tt>PGDATABASE</tt>環境変数から読み取られます。この変数も設定されていない場合は、接続時に指定したユーザ名が使用されます。
</dd>
<dt>
<tt>-t</tt> <tt><i>table</i></tt><br />
<tt>--table</tt> <tt><i>table</i></tt>
</dt>
<dd>オンライン CLUSTER 、または、オンライン VACUUM FULL を行うテーブルを指定します。
このオプションが指定されていない場合は、対象となったデータベースに存在する全ての対象テーブルに対して処理を行います。
</dd>
</dl>
</div>
<div><a name="environment"></a>
<h2>環境変数</h2> <div>
<dl>
<dt>
<tt>PGDATABASE</tt><br />
<tt>PGHOST</tt><br />
<tt>PGPORT</tt><br />
<tt>PGUSER</tt>
</dt>
<dd>デフォルトの接続パラメータです。</dd>
</dl>
</div>
<p>また、このユーティリティは、他のほとんどの PostgreSQL ユーティリティと同様、
libpq でサポートされる環境変数を使用します。詳細については、<a href="http://www.postgresql.jp/document/current/html/libpq-envars.html">環境変数の項目</a>を参照してください。
</p>
</div>
<div><a name="diagnostics"></a>
<h2>トラブルシューティング</h2>
<p>pg_reorg の実行に失敗した場合にエラーが表示されます。
想像されるエラー原因と対処を示します。</p>
<p>致命的なエラーで終了した場合、手動によるクリーンアップを行う必要があります。
クリーンアップは、エラーが発生したデータベースに対して、$PGHOME/share/contrib/uninstall_pg_reorg.sql を実行し、その後、$PGHOME/share/contrib/pg_reorg.sql を実行します。</p>
<dl>
<dt>pg_reorg : reorg database "template1" ... skipped</dt>
<dd><tt>--all</tt>オプションを指定した際に、pg_reorg がインストールされていないデータベースに対して表示されます。</dd>
<dd>pg_reorg スキーマのインストールを行ってください。</dd>
<dt>ERROR: pg_reorg is not installed</dt>
<dd><tt>--dbname</tt>で指定したデータベースにpg_reorg がインストールされていません。</dd>
<dd>pg_reorg のインストールを行ってください。</dd>
<dt>ERROR: relation "table" has no primary key</dt>
<dd>指定したテーブルにPRIMARY KEYが存在していません。</dd>
<dd>対象のテーブルにPRIMARY KEYの作成を行ってください。(ALTER TABLE ADD PRIMARY KEY)</dd>
<dt>ERROR: relation "table" has no cluster key</dt>
<dd>指定したテーブルに CLUSTER KEYが存在していません。</dd>
<dd>対象のテーブルに CLUSTER KEYの作成を行ってください。(ALTER TABLE CLUSTER)</dd>
<dt>pg_reorg : query failed: ERROR: column "col" does not exist</dt>
<dd><tt>--order-by</tt> で指定したカラムが対象のテーブルに存在していません。</dd>
<dd>対象のテーブルに存在するカラムを指定してください。</dd>
<dt>ERROR: permission denied for schema reorg</dt>
<dd>操作を行おうとした対象に権限がありません。</dd>
<dd>スーパーユーザで操作を行ってください。</dd>
<dt>pg_reorg : query failed: ERROR: trigger "z_reorg_trigger" for relation "tbl" already exists</dt>
<dd>操作を行おうとした対象にpg_reorg が処理のために作成するトリガと同名のものが存在しています。</dd>
<dd>トリガの改名か削除を行ってください。</dd>
<dt>pg_reorg : trigger conflicted for tbl</dt>
<dd>操作を行おうとした対象にpg_reorg が処理のために作成するトリガより後に実行されるトリガが存在しています。</dd>
<dd>トリガの改名か削除を行ってください。</dd>
</dl>
</div>
<div><a name="restrictions"></a>
<h2>制約</h2>
<p>pg_reorg を使用する際には、以下の制約があります。以下の制約に関する操作を行った場合の動作は保証されません。注意してください。</p>
<h3>一時テーブルへの操作</h3>
<p>pg_reorg では、一時テーブルは操作の対象外です。</p>
<h3>GiSTインデックスの使用</h3>
<p>インデックス種別がGiSTとなっているインデックスがクラスタインデックスとなっている
テーブルはpg_reorg コマンドを使用して操作を行うことはできません。
これは、GiSTインデックスのソート順序は一意ではないため、<tt>ORDER BY</tt>による
ソートが行えないためです。</p>
<h3>DDLコマンドの発行</h3>
<p>
pg_reorg の実行中には、<tt>VACUUM</tt><tt>ANALYZE</tt> <STRONG>以外</STRONG> のDDL操作は行わないでください。
多くの場合、pg_reorg は失敗しロールバックされます。
しかし、以下の操作ではデータが破損するため、非常に危険です。
</p>
<dl>
<dt><tt>TRUNCATE</tt></dt>
<dd><tt>TRUNCATE</tt>により削除した行が、pg_reorg 実行後には復元しています。操作結果が消失します。</dd>
<dt><tt>CREATE INDEX</tt></dt>
<dd><tt>CREATE INDEX</tt>は、スワップされない索引が残る可能性があります。データの不整合が生じます。</dd>
<dt><tt>ADD COLUMN</tt></dt>
<dd><tt>ADD COLUMN</tt>は、追加された値が全てNULLに置換されてしまう可能性があります。データが消失します。</dd>
<dt><tt>ALTER COLUMN TYPE</tt></dt>
<dd><tt>ALTER COLUMN TYPE</tt>は、実行するとスキーマで定義された型と実際の格納状態に齟齬をきたします。データの不整合が生じます。</dd>
<dt><tt>ALTER TABLE SET TABLESPACE</tt></dt>
<dd><tt>ALTER TABLE SET TABLE SPACE</tt>は、pg_reorg 実行後にrelfilenodeとの不整合が起こるため、対象のテーブルに対する参照/更新操作時にエラーが発生します。</dd>
</dl>
</div>
<div><a name="install"></a>
<h2>インストール方法</h2>
<p>pg_reorg のインストールは、標準のcontribモジュールと同様です。</p>
<h3>ビルド</h3>
<p>pg_reorg のフォルダを$PGHOME/contrib/に配置し、make, make installを行ってください。</p>
<h3>データベースへの登録</h3>
<p>PostgreSQLを起動し、対象のデータベースに対して $PGHOME/share/contrib にある pg_reorg.sql を実行し、インストールを行ってください。</p>
</div>
<div><a name="requirement"></a>
<h2>動作環境</h2>
<dl>
<dt>PostgreSQLバージョン</dt><dd>PostgreSQL 8.3</dd>
<dt>OS</dt><dd>RHEL 5.2, Windows XP SP3</dd>
<dt>ディスク容量</dt><dd>処理対象のテーブル、インデックスサイズの2倍以上のディスク空き容量</dd>
</dl>
</div>
<div><a name="seealso"></a>
<h2>関連項目</h2>
<a href="http://www.postgresql.jp/document/current/html/app-clusterdb.html"><i><tt>clusterdb</tt></i></a>,
<a href="http://www.postgresql.jp/document/current/html/app-vacuumdb.html"><i><tt>vacuumdb</tt></i></a>
</div>
<hr>
<p align="right"><font size="2">
Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
</font></p>
</body>
</html>

275
doc/pg_reorg.html Executable file
View File

@ -0,0 +1,275 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD html 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>pg_reorg</title>
<link rel="home" title="pg_reorg " href="index.html">
<link rel="stylesheet" TYPE="text/css"href="style.css">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<h1><a name="pg_reorg"></a>pg_reorg</h1>
<div><a name="name"></a>
<h2>Name</h2>
pg_reorg -- Reorganize tables in PostgreSQL databases without any locks.
</div>
<div><a name="synopsis"></a>
<h2>Synopsis</h2>
<p>
<tt>pg_reorg</tt> [<tt><i>connection-options</i></tt>...] [<tt><i>message-options</i></tt>...] [<tt><i>order-options</i></tt>...] [<tt><i>target-options</i></tt>...]
</p>
<p>There 4 option categories.
See also <a href="#options">options</a> for details.</p>
<dl>
<dt><tt>connection-options</tt></dt>
<dd><tt>-h</tt> [<tt>--host</tt>] <tt><i>host</i></tt></dd>
<dd><tt>-p</tt> [<tt>--port</tt>] <tt><i>port</i></tt></dd>
<dd><tt>-U</tt> [<tt>--username</tt>] <tt><i>username</i></tt></dd>
<dd><tt>-W</tt> [<tt>--password</tt>]</dd>
<dt><tt>message-options</tt></dt>
<dd><tt>-e</tt> [<tt>--echo</tt>]</dd>
<dd><tt>-q</tt> [<tt>--quiet</tt>]</dd>
<dd><tt>-v</tt> [<tt>--verbose</tt>]</dd>
<dt><tt>order-options</tt></dt>
<dd><tt>-o</tt> [<tt>--order-by</tt>] <tt><i>columns [,...]</i></tt></dd>
<dd><tt>-n</tt> [<tt>--no-order</tt>]</dd>
<dt><tt>target-options</tt></dt>
<dd><tt>-a</tt> [<tt>--all</tt>]</dd>
<dd><tt>-d</tt> [<tt>--dbname</tt>] <tt><i>dbname</i></tt></dd>
<dd><tt>-t</tt> [<tt>--table</tt>] <tt><i>table</i></tt></dd>
</dl>
</div>
<div>
<a name="description"></a>
<h2>Description</h2>
<p>pg_reorg is an utility program to reorganize tables in PostgreSQL databases.
Unlike <a href="http://www.postgresql.jp/document/current/html/app-clusterdb.html"><tt>clusterdb</tt></a>, it doesn't block any selections and updates during reorganization.
You can choose one of the following methods to reorganize.</p>
<ul>
<li>Online CLUSTER (ordered by cluster index)</li>
<li>Ordered by specified columns</li>
<li>Online VACUUM FULL (packing rows only)</li>
</ul>
<p>NOTICE:</p>
<ul>
<li>Only superusers can use the utility.</li>
<li>Target table must have PRIMARY KEY.</li>
<li>You'd better to do <tt><a href="http://www.postgresql.jp/document/current/html/sql-analyze.html">ANALYZE</a></tt> after pg_reorg is completed.</li>
</ul>
</div>
<div><a name="examples"></a>
<h2>Examples</h2>
<p>Execute the following command to do online CLUSTER to all tables in <tt>test</tt> database.</p>
<PRE><SAMP>$ </SAMP><KBD>pg_reorg test</KBD></PRE>
<p>Execute the following command to do online VACUUM FULL to <tt>foo</tt> table in <tt>test</tt> database.</p>
<PRE><SAMP>$ </SAMP><KBD>pg_reorg --no-order --table foo -d test</KBD></PRE><p>
</p></div>
<div><a name="options"></a>
<h2>Options</h2>
<p>pg_reorg has command line options in 4 categolies.</p>
<div>
<dl>
<h3>connection-options</h3>
<p>Parameters to connect PostgreSQL.</p>
<div>
<dl>
<dt><tt>-h <tt><i>host</i></tt><br />
<tt>--host <tt><i>host</i></tt></dt>
<dd>Specifies the host name of the machine on which the server is running. If the value begins with a slash, it is used as the directory for the Unix domain socket. </dd>
<dt><tt>-p <tt><i>port</i></tt><br />
<tt>--port <tt><i>port</i></tt></dt>
<dd>Specifies the TCP port or local Unix domain socket file extension on which the server is listening for connections.</dd>
<dt><tt>-U <tt><i>username</i></tt><br />
<tt>--username <tt><i>username</i></tt></dt>
<dd>User name to connect as. </dd>
<dt><tt>-W</tt><br /><tt>--password</tt></dt>
<dd>Force pg_reorg to prompt for a password before connecting to a database.</dd>
<dd>This option is never essential, since pg_reorg will automatically prompt for a password if the server demands password authentication. However, vacuumdb will waste a connection attempt finding out that the server wants a password. In some cases it is worth typing <tt>-W</tt> to avoid the extra connection attempt. </dd>
</dl>
</div>
<h3>message-options</h3>
<p>Specifies message output by pg_reorg.
<tt>--quiet</tt> is ignored if some of the other options are specified.</p>
<dl>
<dt><tt>-e</tt><br /><tt>--echo</tt></dt>
<dd>Echo the commands that pg_reorg generates and sends to the server.</dd>
<dt><tt>-q</tt><br /><tt>--quiet</tt></dt>
<dd>Do not display progress messages. </dd>
<dt><tt>-v</tt><br /><tt>--verbose</tt></dt>
<dd>Print detailed information during processing.</dd>
</dl>
<h3>order-options</h3>
<p>Options to order rows.
If not specified, pg_reorg do online CLUSTER using cluster indexes.
Only one option can be specified.
</p>
<dl>
<dt><tt>-n</tt><br /><tt>--no-order</tt></dt>
<dd>Do online VACUUM FULL.</dd>
<dt><tt>-o</tt> <tt><i>columns [,...]</i></tt><br />
<tt>--order-by</tt> <tt><i>columns [,...]</i></tt></dt>
<dd>Do online CLUSTER ordered by specified columns.</dd>
</dl>
<h3>target-options</h3>
<p>
Options to specify target tables or databases.
You cannot use <tt>--all</tt> and <tt>--dbname</tt> or <tt>--table</tt> together.
</p>
<dl>
<dt><tt>-a</tt><br /><tt>--all</tt></dt>
<dd>Reorganize all databases.</dd>
<dt>
<tt>-d <tt><i>dbname</i></tt><br />
<tt>--dbname <tt><i>dbname</i></tt>
</dt>
<dd>Specifies the name of the database to be reorganized. If this is not specified and <tt>-a</tt> (or <tt>--all</tt>) is not used, the database name is read from the environment variable <tt>PGDATABASE</tt>. If that is not set, the user name specified for the connection is used. </dd>
<dt>
<tt>-t</tt> <tt><i>table</i></tt><br />
<tt>--table</tt> <tt><i>table</i></tt>
</dt>
<dd>Reorganize <tt><i>table</i></tt> only. If you don't specify this option, all tables in specified databases are reorganized.</dd>
</dl>
</div>
<div><a name="environment"></a>
<h2>Environment</h2> <div>
<dl>
<dt>
<tt>PGDATABASE</tt><br />
<tt>PGHOST</tt><br />
<tt>PGPORT</tt><br />
<tt>PGUSER</tt>
</dt>
<dd>Default connection parameters</dd>
</dl>
</div>
<p>This utility, like most other PostgreSQL utilities, also uses the environment variables supported by libpq (see <a href="http://developer.postgresql.org/pgdocs/postgres/libpq-envars.html">Environment Variables</a>).</p>
</div>
<div><a name="diagnostics"></a>
<h2>Diagnostics</h2>
<p>Error messages are reported when pg_reorg fails.
The following list shows the cause of errors.</p>
<p>You need to cleanup by hand after fatal erros.
To cleanup, execute $PGHOME/share/contrib/uninstall_pg_reorg.sql to the database where the error occured and then execute $PGHOME/share/contrib/pg_reorg.sql. (Do uninstall and reinstall.)</p>
<dl>
<dt>pg_reorg : reorg database "template1" ... skipped</dt>
<dd>pg_reorg is not installed in the database when <tt>--all</tt> option is specified.</dd>
<dd>Do register pg_reorg to the database.</dd>
<dt>ERROR: pg_reorg is not installed</dt>
<dd>pg_reorg is not installed in the database specified by <tt>--dbname</tt>.</dd>
<dd>Do register pg_reorg to the database.</dd>
<dt>ERROR: relation "table" has no primary key</dt>
<dd>The target table doesn't have PRIMARY KEY.</dd>
<dd>Define PRIMARY KEY to the table. (ALTER TABLE ADD PRIMARY KEY)</dd>
<dt>ERROR: relation "table" has no cluster key</dt>
<dd>The target table doesn't have CLUSTER KEY.</dd>
<dd>Define CLUSTER KEY to the table. (ALTER TABLE CLUSTER)</dd>
<dt>pg_reorg : query failed: ERROR: column "col" does not exist</dt>
<dd>The target table doesn't have columns specified by <tt>--order-by</tt> option.</dd>
<dd>Specify existing columns.</dd>
<dt>ERROR: permission denied for schema reorg</dt>
<dd>Permission error.</dd>
<dd>pg_reorg must be executed by superusers.</dd>
<dt>pg_reorg : query failed: ERROR: trigger "z_reorg_trigger" for relation "tbl" already exists</dt>
<dd>The target table already has a trigger named "z_reorg_trigger".</dd>
<dd>Delete or rename the trigger.</dd>
<dt>pg_reorg : trigger conflicted for tbl</dt>
<dd>The target table already has a trigger which follows by "z_reorg_trigger" in alphabetical order.</dd>
<dd>Delete or rename the trigger.</dd>
</dl>
</div>
<div><a name="restrictions"></a>
<h2>Restrictions</h2>
<p>pg_reorg has the following restrictions.
Be careful to avoid data corruptions.</p>
<h3>Temp tables</h3>
<p>pg_reorg cannot reorganize temp tables.</p>
<h3>GiST indexes</h3>
<p>pg_reorg cannot reorganize tables using GiST indexes.</p>
<h3>DDL commands</h3>
<p>You cannot do DDL commands <strong>except</strong> <tt>VACUUM</tt> and <tt>ANALYZE</tt> during pg_reorg.
In many case pg_reorg would fail and rollback collectly, but there are some cases ending with data-corruption .</p>
<dl>
<dt><tt>TRUNCATE</tt></dt>
<dd><tt>TRUNCATE</tt> is lost. Deleted rows still exist after pg_reorg.</dd>
<dt><tt>CREATE INDEX</tt></dt>
<dd><tt>CREATE INDEX</tt> causes index corruptions.</dd>
<dt><tt>ADD COLUMN</tt></dt>
<dd><tt>ADD COLUMN</tt> causes lost of data. Newly added columns are initialized with NULLs.</dd>
<dt><tt>ALTER COLUMN TYPE</tt></dt>
<dd><tt>ALTER COLUMN TYPE</tt> cases data coruuptions.</dd>
<dt><tt>ALTER TABLE SET TABLESPACE</tt></dt>
<dd><tt>ALTER TABLE SET TABLE SPACE</tt> cases data corruptions by wrong relfilenode.</dd>
</dl>
</div>
<div><a name="install"></a>
<h2>Installations</h2>
<p>pg_reorg can be installed like standard contrib modules.</p>
<h3>Build from source</h3>
<p>Place pg_reorg to $PGHOME/contrib/ and input make, make install.</p>
<h3>Register to database</h3>
<p>Start PostgreSQL and execute pg_reorg.sql in $PGHOME/share/contrib.</p>
</div>
<div><a name="requirement"></a>
<h2>Requirements</h2>
<dl>
<dt>PostgreSQL version</dt><dd>PostgreSQL 8.3</dd>
<dt>OS</dt><dd>RHEL 5.2, Windows XP SP3</dd>
<dt>Disks</dt><dd>Requires amount of disks twice larger than target table and indexes.</dd>
</dl>
</div>
<div><a name="seealso"></a>
<h2>See Also</h2>
<a href="http://developer.postgresql.org/pgdocs/postgres/app-clusterdb.html"><i><tt>clusterdb</tt></i></a>,
<a href="http://developer.postgresql.org/pgdocs/postgres/app-vacuumdb.html"><i><tt>vacuumdb</tt></i></a>
</div>
<hr>
<p align="right"><font size="2">
Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
</font></p>
</body>
</html>

106
doc/style.css Executable file
View File

@ -0,0 +1,106 @@
BODY {
margin-top: 3;
margin-left: 3;
margin-right: 3;
margin-bottom: 3;
}
TT,OL,UL,P,BODY,TR,TD,TH,FORM {
font-family: arial,helvetica,sans-serif;
color: #202020;
}
/* give the rule a bit of extra space (above and below), since its being used to divide
sections on some pages (project summary) */
HR { margin: 5px 0px 5px 0px }
h2, h3, h4, h5, h6 {
color: Black;
background: none;
padding-top: 0.5em;
padding-bottom: 0.17em;
border-bottom: 1px solid #aaaaaa;
}
H1 { font-size: x-large; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
H2 { font-size: large; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
H3 { padding-left: 1em; font-size: medium; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
H4 { padding-left: 2em; font-size: small; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
H5 { padding-left: 3em; font-size: x-small; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
H6 { padding-left: 4em; font-size: xx-small; font-family: Lucida Grande,verdana,arial,helvetica,sans-serif; }
dt {
font-size: 110%;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
PRE {
font-family: courier,sans-serif;
background-color: #FBFBFD;
border: 1px dashed #7E7ECB;
color: black;
line-height: 1.1em; padding: 0.5em;
overflow: auto;
}
A:link { text-decoration:none; color:#0000EE }
A:visited { text-decoration:none color:#551A8B}
A:active { text-decoration:none; color:#00ff00 }
A:hover { text-decoration:underline; color:#008000 }
.titlebar { color: #000000; text-decoration: none; font-weight: bold; }
A.tablink {
color: #000000;
text-decoration: none;
font-weight: bold;
font-size: small;
}
A.tablink:visited {
color: #000000;
text-decoration: none;
font-weight: bold;
font-size: small;
}
A.tablink:hover {
text-decoration: none;
color: #000000;
font-weight: bold;
font-size: small;
}
A.tabsellink {
color: #ffffcc;
text-decoration: none;
font-weight: bold;
font-size: small;
}
A.tabsellink:visited {
color: #ffffcc;
text-decoration: none;
font-weight: bold;
font-size: small;
}
A.tabsellink:hover {
text-decoration: none;
color: #ffffcc;
font-weight: bold;
font-size: small;
}
A.showsource {
color: #000000;
text-decoration: none;
font-size: small;
}
A.showsource:visited {
color: #000000;
text-decoration: none;
font-size: small;
}
A.showsource:hover {
color: #000000;
text-decoration: none;
font-size: small;
}

20
lib/Makefile Executable file
View File

@ -0,0 +1,20 @@
#
# pg_reorg: lib/Makefile
#
# Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
#
SRCS = reorg.c
OBJS = $(SRCS:.c=.o)
MODULE_big = pg_reorg
DATA_built = pg_reorg.sql
DATA = uninstall_pg_reorg.sql
ifdef USE_PGXS
PGXS := $(shell pg_config --pgxs)
include $(PGXS)
else
subdir = contrib/pg_reorg
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

158
lib/pg_reorg.sql.in Executable file
View File

@ -0,0 +1,158 @@
/*
* pg_reorg: lib/pg_reorg.sql.in
*
* Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*/
-- Adjust this setting to control where the objects get created.
SET search_path = public;
BEGIN;
CREATE SCHEMA reorg;
CREATE AGGREGATE reorg.array_accum (
sfunc = array_append,
basetype = anyelement,
stype = anyarray,
initcond = '{}'
);
CREATE FUNCTION reorg.get_index_columns(oid, text) RETURNS text AS
$$
SELECT array_to_string(reorg.array_accum(quote_ident(attname)), $2)
FROM pg_attribute,
(SELECT indrelid,
indkey,
generate_series(0, indnatts-1) AS i
FROM pg_index
WHERE indexrelid = $1
) AS keys
WHERE attrelid = indrelid
AND attnum = indkey[i];
$$
LANGUAGE sql STABLE STRICT;
CREATE FUNCTION reorg.get_index_keys(oid, oid) RETURNS text AS
'MODULE_PATHNAME', 'reorg_get_index_keys'
LANGUAGE 'C' STABLE STRICT;
CREATE FUNCTION reorg.get_create_index_type(oid, name) RETURNS text AS
$$
SELECT 'CREATE TYPE ' || $2 || ' AS (' ||
array_to_string(reorg.array_accum(quote_ident(attname) || ' ' ||
pg_catalog.format_type(atttypid, atttypmod)), ', ') || ')'
FROM pg_attribute,
(SELECT indrelid,
indkey,
generate_series(0, indnatts-1) AS i
FROM pg_index
WHERE indexrelid = $1
) AS keys
WHERE attrelid = indrelid
AND attnum = indkey[i];
$$
LANGUAGE sql STABLE STRICT;
CREATE FUNCTION reorg.get_create_trigger(relid oid, pkid oid)
RETURNS text AS
$$
SELECT 'CREATE TRIGGER z_reorg_trigger' ||
' BEFORE INSERT OR DELETE OR UPDATE ON ' || $1::regclass ||
' FOR EACH ROW EXECUTE PROCEDURE reorg.reorg_trigger(' ||
'''INSERT INTO reorg.log_' || $1 || '(pk, row) VALUES(' ||
' CASE WHEN $1 IS NULL THEN NULL ELSE (ROW($1.' ||
reorg.get_index_columns($2, ', $1.') || ')::reorg.pk_' ||
$1 || ') END, $2)'')';
$$
LANGUAGE sql STABLE STRICT;
CREATE FUNCTION reorg.get_assign(oid, text) RETURNS text AS
$$
SELECT '(' || array_to_string(reorg.array_accum(attname), ', ') ||
') = (' || $2 || '.' ||
array_to_string(reorg.array_accum(quote_ident(attname)), ', ' || $2 || '.') || ')'
FROM (SELECT attname FROM pg_attribute
WHERE attrelid = $1 AND attnum > 0
ORDER BY attnum) tmp;
$$
LANGUAGE sql STABLE STRICT;
CREATE FUNCTION reorg.get_compare_pkey(oid, text)
RETURNS text AS
$$
SELECT '(' || array_to_string(reorg.array_accum(quote_ident(attname)), ', ') ||
') = (' || $2 || '.' ||
array_to_string(reorg.array_accum(quote_ident(attname)), ', ' || $2 || '.') || ')'
FROM pg_attribute,
(SELECT indrelid,
indkey,
generate_series(0, indnatts-1) AS i
FROM pg_index
WHERE indexrelid = $1
) AS keys
WHERE attrelid = indrelid
AND attnum = indkey[i];
$$
LANGUAGE sql STABLE STRICT;
CREATE VIEW reorg.tables AS
SELECT R.oid::regclass AS relname,
R.oid AS relid,
R.reltoastrelid AS reltoastrelid,
CASE WHEN R.reltoastrelid = 0 THEN 0 ELSE (SELECT reltoastidxid FROM pg_class WHERE oid = R.reltoastrelid) END AS reltoastidxid,
PK.indexrelid AS pkid,
CK.indexrelid AS ckid,
reorg.get_create_index_type(PK.indexrelid, 'reorg.pk_' || R.oid) AS create_pktype,
'CREATE TABLE reorg.log_' || R.oid || ' (id bigserial PRIMARY KEY, pk reorg.pk_' || R.oid || ', row ' || R.oid::regclass || ')' AS create_log,
reorg.get_create_trigger(R.oid, PK.indexrelid) AS create_trigger,
'CREATE TABLE reorg.table_' || R.oid || ' WITH (' || array_to_string(array_append(R.reloptions, 'oids=' || R.relhasoids), ',') || ') TABLESPACE ' || coalesce(quote_ident(S.spcname), 'pg_default') || ' AS SELECT * FROM ONLY ' || R.oid::regclass AS create_table,
'DELETE FROM reorg.log_' || R.oid AS delete_log,
'LOCK TABLE ' || R.oid::regclass || ' IN ACCESS EXCLUSIVE MODE' AS lock_table,
reorg.get_index_keys(CK.indexrelid, R.oid) AS ckey,
'SELECT * FROM reorg.log_' || R.oid || ' ORDER BY id LIMIT $1' AS sql_peek,
'INSERT INTO reorg.table_' || R.oid || ' VALUES ($1.*)' AS sql_insert,
'DELETE FROM reorg.table_' || R.oid || ' WHERE ' || reorg.get_compare_pkey(PK.indexrelid, '$1') AS sql_delete,
'UPDATE reorg.table_' || R.oid || ' SET ' || reorg.get_assign(R.oid, '$2') || ' WHERE ' || reorg.get_compare_pkey(PK.indexrelid, '$1') AS sql_update,
'DELETE FROM reorg.log_' || R.oid || ' WHERE id <= $1' AS sql_pop
FROM pg_class R
LEFT JOIN pg_class T ON R.reltoastrelid = T.oid
LEFT JOIN (SELECT * FROM pg_index WHERE indisprimary) PK
ON R.oid = PK.indrelid
LEFT JOIN (SELECT CKI.* FROM pg_index CKI, pg_class CKT
WHERE CKI.indexrelid = CKT.oid AND CKI.indisclustered AND CKT.relam = 403) CK
ON R.oid = CK.indrelid
LEFT JOIN pg_namespace N ON N.oid = R.relnamespace
LEFT JOIN pg_tablespace S ON S.oid = R.reltablespace
WHERE R.relkind = 'r'
AND N.nspname NOT IN ('pg_catalog', 'information_schema')
AND N.nspname NOT LIKE E'pg\\_temp\\_%';
CREATE FUNCTION reorg.reorg_indexdef(oid, oid) RETURNS text AS
'MODULE_PATHNAME', 'reorg_indexdef'
LANGUAGE 'C' STABLE STRICT;
CREATE FUNCTION reorg.reorg_trigger() RETURNS trigger AS
'MODULE_PATHNAME', 'reorg_trigger'
LANGUAGE 'C' VOLATILE STRICT SECURITY DEFINER;
CREATE FUNCTION reorg.reorg_apply(
sql_peek cstring,
sql_insert cstring,
sql_delete cstring,
sql_update cstring,
sql_pop cstring,
count integer)
RETURNS integer AS
'MODULE_PATHNAME', 'reorg_apply'
LANGUAGE 'C' VOLATILE;
CREATE FUNCTION reorg.reorg_swap(oid) RETURNS void AS
'MODULE_PATHNAME', 'reorg_swap'
LANGUAGE 'C' VOLATILE STRICT;
CREATE FUNCTION reorg.reorg_drop(oid) RETURNS void AS
'MODULE_PATHNAME', 'reorg_drop'
LANGUAGE 'C' VOLATILE STRICT;
COMMIT;

677
lib/reorg.c Executable file
View File

@ -0,0 +1,677 @@
/*
* pg_reorg: lib/reorg.c
*
* Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*/
/**
* @brief Core Modules
*/
#include "postgres.h"
#include "catalog/namespace.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "utils/lsyscache.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
#if PG_VERSION_NUM < 80300
#if PG_VERSION_NUM < 80300
#define SET_VARSIZE(PTR, len) (VARATT_SIZEP((PTR)) = (len))
typedef void *SPIPlanPtr;
#endif
#endif
Datum reorg_trigger(PG_FUNCTION_ARGS);
Datum reorg_apply(PG_FUNCTION_ARGS);
Datum reorg_get_index_keys(PG_FUNCTION_ARGS);
Datum reorg_indexdef(PG_FUNCTION_ARGS);
Datum reorg_swap(PG_FUNCTION_ARGS);
Datum reorg_drop(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(reorg_trigger);
PG_FUNCTION_INFO_V1(reorg_apply);
PG_FUNCTION_INFO_V1(reorg_get_index_keys);
PG_FUNCTION_INFO_V1(reorg_indexdef);
PG_FUNCTION_INFO_V1(reorg_swap);
PG_FUNCTION_INFO_V1(reorg_drop);
static void reorg_init(void);
static SPIPlanPtr reorg_prepare(const char *src, int nargs, Oid *argtypes);
static void reorg_execp(SPIPlanPtr plan, Datum *values, const char *nulls, int expected);
static void reorg_execf(int expexted, const char *format, ...)
__attribute__((format(printf, 2, 3)));
#define copy_tuple(tuple, tupdesc) \
PointerGetDatum(SPI_returntuple((tuple), (tupdesc)))
/* check access authority */
static void
must_be_superuser(const char *func)
{
if (!superuser())
elog(ERROR, "must be superuser to use %s function", func);
}
#if PG_VERSION_NUM < 80400
static int
SPI_execute_with_args(const char *src,
int nargs, Oid *argtypes,
Datum *values, const char *nulls,
bool read_only, long tcount)
{
SPIPlanPtr plan;
int ret;
plan = SPI_prepare(src, nargs, argtypes);
if (plan == NULL)
return SPI_result;
ret = SPI_execute_plan(plan, values, nulls, read_only, tcount);
SPI_freeplan(plan);
return ret;
}
static text *
cstring_to_text(const char * s)
{
int len = strlen(s);
text *result = palloc(len + VARHDRSZ);
SET_VARSIZE(result, len + VARHDRSZ);
memcpy(VARDATA(result), s, len);
return result;
}
#endif
/**
* @fn Datum reorg_trigger(PG_FUNCTION_ARGS)
* @brief Insert a operation log into log-table.
*
* reorg_trigger(sql)
*
* @param sql SQL to insert a operation log into log-table.
*/
Datum
reorg_trigger(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
TupleDesc tupdesc;
HeapTuple tuple;
Datum values[2];
char nulls[2] = { ' ', ' ' };
Oid argtypes[2];
const char *sql;
int ret;
/* authority check */
must_be_superuser("reorg_trigger");
/* 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)
elog(ERROR, "reorg_trigger: invalid trigger call");
/* retrieve parameters */
sql = trigdata->tg_trigger->tgargs[0];
tupdesc = RelationGetDescr(trigdata->tg_relation);
argtypes[0] = argtypes[1] = trigdata->tg_relation->rd_rel->reltype;
/* connect to SPI manager */
reorg_init();
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
/* INSERT: (NULL, newtup) */
tuple = trigdata->tg_trigtuple;
nulls[0] = 'n';
values[1] = copy_tuple(tuple, tupdesc);
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
/* DELETE: (oldtup, NULL) */
tuple = trigdata->tg_trigtuple;
values[0] = copy_tuple(tuple, tupdesc);
nulls[1] = 'n';
}
else
{
/* UPDATE: (oldtup, newtup) */
tuple = trigdata->tg_newtuple;
values[0] = copy_tuple(trigdata->tg_trigtuple, tupdesc);
values[1] = copy_tuple(tuple, tupdesc);
}
/* INSERT INTO reorg.log VALUES ($1, $2) */
ret = SPI_execute_with_args(sql, 2, argtypes, values, nulls, false, 1);
if (ret < 0)
elog(ERROR, "reorg_trigger: SPI_execp returned %d", ret);
SPI_finish();
PG_RETURN_POINTER(tuple);
}
/**
* @fn Datum reorg_apply(PG_FUNCTION_ARGS)
* @brief Apply operations in log table into temp table.
*
* reorg_apply(sql_peek, sql_insert, sql_delete, sql_update, sql_pop, count)
*
* @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
reorg_apply(PG_FUNCTION_ARGS)
{
#define NUMBER_OF_PROCESSING 1000
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;
TupleDesc tupdesc;
Oid argtypes[3]; /* id, pk, row */
Datum values[3]; /* id, pk, row */
char nulls[3]; /* id, pk, row */
Oid argtypes_peek[1] = { INT4OID };
Datum values_peek[1];
char nulls_peek[1] = { ' ' };
/* authority check */
must_be_superuser("reorg_apply");
/* connect to SPI manager */
reorg_init();
/* peek tuple in log */
plan_peek = reorg_prepare(sql_peek, 1, argtypes_peek);
for (n = 0;;)
{
int ntuples;
SPITupleTable *tuptable;
/* peek tuple in log */
if (count == 0)
values_peek[0] = Int32GetDatum(NUMBER_OF_PROCESSING);
else
values_peek[0] = Int32GetDatum(Min(count - n, NUMBER_OF_PROCESSING));
reorg_execp(plan_peek, values_peek, nulls_peek, SPI_OK_SELECT);
if (SPI_processed <= 0)
break;
/* copy tuptable because we will call other sqls. */
ntuples = SPI_processed;
tuptable = SPI_tuptable;
tupdesc = tuptable->tupdesc;
argtypes[0] = SPI_gettypeid(tupdesc, 1); /* id */
argtypes[1] = SPI_gettypeid(tupdesc, 2); /* pk */
argtypes[2] = SPI_gettypeid(tupdesc, 3); /* row */
for (i = 0; i < ntuples; i++, n++)
{
HeapTuple tuple;
bool isnull;
tuple = tuptable->vals[i];
values[0] = SPI_getbinval(tuple, tupdesc, 1, &isnull);
nulls[0] = ' ';
values[1] = SPI_getbinval(tuple, tupdesc, 2, &isnull);
nulls[1] = (isnull ? 'n' : ' ');
values[2] = SPI_getbinval(tuple, tupdesc, 3, &isnull);
nulls[2] = (isnull ? 'n' : ' ');
if (nulls[1] == 'n')
{
/* INSERT */
if (plan_insert == NULL)
plan_insert = reorg_prepare(sql_insert, 1, &argtypes[2]);
reorg_execp(plan_insert, &values[2], &nulls[2], SPI_OK_INSERT);
}
else if (nulls[2] == 'n')
{
/* DELETE */
if (plan_delete == NULL)
plan_delete = reorg_prepare(sql_delete, 1, &argtypes[1]);
reorg_execp(plan_delete, &values[1], &nulls[1], SPI_OK_DELETE);
}
else
{
/* UPDATE */
if (plan_update == NULL)
plan_update = reorg_prepare(sql_update, 2, &argtypes[1]);
reorg_execp(plan_update, &values[1], &nulls[1], SPI_OK_UPDATE);
}
}
/* delete tuple in log */
if (plan_pop == NULL)
plan_pop = reorg_prepare(sql_pop, 1, argtypes);
reorg_execp(plan_pop, values, nulls, SPI_OK_DELETE);
SPI_freetuptable(tuptable);
}
SPI_finish();
PG_RETURN_INT32(n);
}
/*
* Deparsed create index sql. You can rebuild sql using
* 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 *
generate_relation_name(Oid relid)
{
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)
{
elog(ERROR, "Unexpected indexdef: %s", pg_get_indexdef_string(index));
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)
{
sql = strchr(sql, ' ');
if (sql)
{
*sql = '\0';
return sql + 2;
}
/* error */
return parse_error(index);
}
static char *
skip_columns(Oid index, char *sql)
{
char instr = 0;
int nopen = 1;
for (; *sql && nopen > 0; sql++)
{
if (instr)
{
if (sql[0] == instr)
{
if (sql[1] == instr)
sql++;
else
instr = 0;
}
else if (sql[0] == '\\')
{
sql++; // next char is always string
}
}
else
{
switch (sql[0])
{
case '(':
nopen++;
break;
case ')':
nopen--;
break;
case '\'':
case '"':
instr = sql[0];
break;
}
}
}
if (nopen == 0)
{
sql[-1] = '\0';
return sql;
}
/* error */
return parse_error(index);
}
static void
parse_indexdef(IndexDef *stmt, Oid index, Oid table)
{
char *sql = pg_get_indexdef_string(index);
const char *idxname = quote_identifier(get_rel_name(index));
const char *tblname = generate_relation_name(table);
/* 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 */
stmt->type= sql;
sql = skip_ident(index, sql);
/* (columns) */
stmt->columns = sql;
sql = skip_columns(index, sql);
/* options */
stmt->options = sql;
}
/**
* @fn Datum reorg_get_index_keys(PG_FUNCTION_ARGS)
* @brief Get key definition of the index.
*
* reorg_get_index_keys(index, table)
*
* @param index Oid of target index.
* @param table Oid of table of the index.
* @retval Create index DDL for temp table.
*/
Datum
reorg_get_index_keys(PG_FUNCTION_ARGS)
{
Oid index = PG_GETARG_OID(0);
Oid table = PG_GETARG_OID(1);
IndexDef stmt;
parse_indexdef(&stmt, index, table);
PG_RETURN_TEXT_P(cstring_to_text(stmt.columns));
}
/**
* @fn Datum reorg_indexdef(PG_FUNCTION_ARGS)
* @brief Reproduce DDL that create index at the temp table.
*
* reorg_indexdef(index, table)
*
* @param index Oid of target index.
* @param table Oid of table of the index.
* @retval Create index DDL for temp table.
*/
Datum
reorg_indexdef(PG_FUNCTION_ARGS)
{
Oid index = PG_GETARG_OID(0);
Oid table = PG_GETARG_OID(1);
IndexDef stmt;
StringInfoData str;
parse_indexdef(&stmt, index, table);
initStringInfo(&str);
appendStringInfo(&str, "%s index_%u ON reorg.table_%u USING %s (%s)%s",
stmt.create, index, table, stmt.type, stmt.columns, stmt.options);
PG_RETURN_TEXT_P(cstring_to_text(str.data));
}
#define SQL_GET_SWAPINFO "\
SELECT X.oid, X.relfilenode, X.relfrozenxid, Y.oid, Y.relfilenode, Y.relfrozenxid \
FROM pg_class X, pg_class Y \
WHERE X.oid = $1\
AND Y.oid = ('reorg.table_' || X.oid)::regclass \
UNION ALL \
SELECT T.oid, T.relfilenode, T.relfrozenxid, U.oid, U.relfilenode, U.relfrozenxid \
FROM pg_class X, pg_class T, pg_class Y, pg_class U \
WHERE X.oid = $1\
AND T.oid = X.reltoastrelid\
AND Y.oid = ('reorg.table_' || X.oid)::regclass\
AND U.oid = Y.reltoastrelid \
UNION ALL \
SELECT I.oid, I.relfilenode, I.relfrozenxid, J.oid, J.relfilenode, J.relfrozenxid \
FROM pg_class X, pg_class T, pg_class I, pg_class Y, pg_class U, pg_class J \
WHERE X.oid = $1\
AND T.oid = X.reltoastrelid\
AND I.oid = T.reltoastidxid\
AND Y.oid = ('reorg.table_' || X.oid)::regclass\
AND U.oid = Y.reltoastrelid\
AND J.oid = U.reltoastidxid \
UNION ALL \
SELECT X.oid, X.relfilenode, X.relfrozenxid, Y.oid, Y.relfilenode, Y.relfrozenxid \
FROM pg_index I, pg_class X, pg_class Y \
WHERE I.indrelid = $1\
AND I.indexrelid = X.oid\
AND Y.oid = ('reorg.index_' || X.oid)::regclass\
ORDER BY 1\
"
/**
* @fn Datum reorg_swap(PG_FUNCTION_ARGS)
* @brief Swapping relfilenode of table, toast, toast index
* and table indexes on target table and temp table mutually.
*
* reorg_swap(oid, relname)
*
* @param oid Oid of table of target.
* @retval None.
*/
Datum
reorg_swap(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
SPIPlanPtr plan_swapinfo;
SPIPlanPtr plan_swap;
Oid argtypes[3] = { OIDOID, OIDOID, XIDOID };
char nulls[3] = { ' ', ' ', ' ' };
Datum values[3];
int record;
/* authority check */
must_be_superuser("reorg_swap");
/* connect to SPI manager */
reorg_init();
/* parepare */
plan_swapinfo = reorg_prepare(
SQL_GET_SWAPINFO,
1, argtypes);
plan_swap = reorg_prepare(
"UPDATE pg_class SET relfilenode = $2, relfrozenxid = $3 WHERE oid = $1",
3, argtypes);
/* swap relfilenode */
values[0] = ObjectIdGetDatum(oid);
reorg_execp(plan_swapinfo, values, nulls, SPI_OK_SELECT);
record = SPI_processed;
if (record > 0)
{
SPITupleTable *tuptable;
TupleDesc tupdesc;
HeapTuple tuple;
char isnull;
int i;
tuptable = SPI_tuptable;
tupdesc = tuptable->tupdesc;
for (i = 0; i < record; i++)
{
tuple = tuptable->vals[i];
/* target -> temp */
values[0] = SPI_getbinval(tuple, tupdesc, 4, &isnull);
values[1] = SPI_getbinval(tuple, tupdesc, 2, &isnull);
values[2] = SPI_getbinval(tuple, tupdesc, 3, &isnull);
reorg_execp(plan_swap, values, nulls, SPI_OK_UPDATE);
/* temp -> target */
values[0] = SPI_getbinval(tuple, tupdesc, 1, &isnull);
values[1] = SPI_getbinval(tuple, tupdesc, 5, &isnull);
values[2] = SPI_getbinval(tuple, tupdesc, 6, &isnull);
reorg_execp(plan_swap, values, nulls, SPI_OK_UPDATE);
}
}
SPI_finish();
PG_RETURN_VOID();
}
/**
* @fn Datum reorg_drop(PG_FUNCTION_ARGS)
* @brief Delete temporarily objects.
*
* reorg_drop(oid, relname)
*
* @param oid Oid of target table.
* @retval None.
*/
Datum
reorg_drop(PG_FUNCTION_ARGS)
{
Oid oid = PG_GETARG_OID(0);
const char *relname = quote_identifier(get_rel_name(oid));
const char *nspname = quote_identifier(get_namespace_name(get_rel_namespace(oid)));
/* authority check */
must_be_superuser("reorg_drop");
/* connect to SPI manager */
reorg_init();
/* drop reorg trigger */
reorg_execf(
SPI_OK_UTILITY,
"DROP TRIGGER IF EXISTS z_reorg_trigger ON %s.%s CASCADE",
nspname, relname);
/* drop log table */
reorg_execf(
SPI_OK_UTILITY,
"DROP TABLE IF EXISTS reorg.log_%u CASCADE",
oid);
/* drop temp table */
reorg_execf(
SPI_OK_UTILITY,
"DROP TABLE IF EXISTS reorg.table_%u CASCADE",
oid);
/* drop type for log table */
reorg_execf(
SPI_OK_UTILITY,
"DROP TYPE IF EXISTS reorg.pk_%u CASCADE",
oid);
SPI_finish();
PG_RETURN_VOID();
}
/* init SPI */
static void
reorg_init(void)
{
int ret = SPI_connect();
if (ret != SPI_OK_CONNECT)
elog(ERROR, "pg_reorg: SPI_connect returned %d", ret);
}
/* prepare plan */
static SPIPlanPtr
reorg_prepare(const char *src, int nargs, Oid *argtypes)
{
SPIPlanPtr plan = SPI_prepare(src, nargs, argtypes);
if (plan == NULL)
elog(ERROR, "pg_reorg: reorg_prepare failed (code=%d, query=%s)", SPI_result, src);
return plan;
}
/* execute prepared plan */
static void
reorg_execp(SPIPlanPtr plan, Datum *values, const char *nulls, int expected)
{
int ret = SPI_execute_plan(plan, values, nulls, false, 0);
if (ret != expected)
elog(ERROR, "pg_reorg: reorg_execp failed (code=%d, expected=%d)", ret, expected);
}
/* execute sql with format */
static void
reorg_execf(int expected, const char *format, ...)
{
va_list ap;
StringInfoData sql;
int ret;
initStringInfo(&sql);
va_start(ap, format);
appendStringInfoVA(&sql, format, ap);
va_end(ap);
if ((ret = SPI_exec(sql.data, 0)) != expected)
elog(ERROR, "pg_reorg: reorg_execf failed (sql=%s, code=%d, expected=%d)", sql.data, ret, expected);
}

7
lib/uninstall_pg_reorg.sql Executable file
View File

@ -0,0 +1,7 @@
/*
* pg_reorg: lib/uninstall_reorg.sql
*
* Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*/
DROP SCHEMA IF EXISTS reorg CASCADE;