commit 8af8be23ac4d6c66fb8f82d7aea057c319f64e1d Author: Takahiro Itagaki Date: Mon Dec 8 04:32:10 2008 +0000 Initial revision diff --git a/Makefile b/Makefile new file mode 100755 index 0000000..dcbda14 --- /dev/null +++ b/Makefile @@ -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 diff --git a/bin/Makefile b/bin/Makefile new file mode 100755 index 0000000..0d24d7b --- /dev/null +++ b/bin/Makefile @@ -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 diff --git a/bin/pg_reorg.c b/bin/pg_reorg.c new file mode 100755 index 0000000..ca4617b --- /dev/null +++ b/bin/pg_reorg.c @@ -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 +#include + + +#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 */ diff --git a/doc/index-ja.html b/doc/index-ja.html new file mode 100755 index 0000000..71b8246 --- /dev/null +++ b/doc/index-ja.html @@ -0,0 +1,53 @@ + + + + + + + + + pg_reorg: Project Home Page + + + +
+
+

pg_reorg ホームページへようこそ

+
+
+

+pg_reorg は PostgreSQL のテーブルを再編成するシェルコマンドです。 +共有ロックや排他ロックを取得しないため、再編成中であっても行の参照や更新を行うことができます。 +このモジュールは CLUSTER や VACUUM FULL コマンドのより良い代替になります。 +

+

この pg_reorg プロジェクトは PostgreSQL コミュニティによる pgFoundry の中のプロジェクトです。

+ +
+Here is an English page. +
+
+ +

+ドキュメントはこちら。 +

+ +
+
+Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +
+ + +
+
+ +
+ + + diff --git a/doc/index.html b/doc/index.html new file mode 100755 index 0000000..a5f64bf --- /dev/null +++ b/doc/index.html @@ -0,0 +1,53 @@ + + + + + + + + + pg_reorg: Project Home Page + + + +
+
+

Welcome to the pg_reorg Project Home Page

+
+
+

+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. +

+

+The pg_reorg project is a PostgreSQL Community project that is a part of the pgFoundry. +

+

+The pgFoundry page for the project is at http://pgfoundry.org/projects/reorg, +where you can find downloads, documentation, bug reports, mailing lists, and a whole lot more. +

+
+日本語のページはこちら。 +
+
+ +

+Documentations here. +

+ +
+
+Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +
+ + +
+
+ +
+ + + diff --git a/doc/pg_reorg-ja.html b/doc/pg_reorg-ja.html new file mode 100755 index 0000000..09b80ab --- /dev/null +++ b/doc/pg_reorg-ja.html @@ -0,0 +1,291 @@ + + + +pg_reorg + + + + + + +

pg_reorg

+ +
+

名前

+pg_reorg -- PostgreSQLデータベース内のテーブルに対して、参照/更新処理をブロックせずに再編成を行います。 +
+ +
+ +

概要

+

+pg_reorg [connection-options...] [message-options...] [order-options...] [target-options...] +

+

指定できるオプションには4つのカテゴリがあります。 +詳細はオプションを参照してください。

+
+
connection-options : 接続パラメータ
+
-h [--host] host
+
-p [--port] port
+
-U [--username] username
+
-W [--password]
+
message-options : 出力メッセージ
+
-e [--echo]
+
-q [--quiet]
+
-v [--verbose]
+
order-options : 並び替えの基準
+
-o [--order-by] columns [,...]
+
-n [--no-order]
+
target-options : 処理対象
+
-a [--all]
+
-d [--dbname] dbname
+
-t [--table] table
+
+
+ +
+

説明

+

pg_reorg は、PostgreSQLデータベース内のテーブルを再編成(行の並び替え)するユーティリティです。 +clusterdb と異なり、参照/更新処理をブロックしません。 +再編成の方式として、以下のいずれか1つを選択できます。

+
    +
  • オンライン CLUSTER (cluster index順に行を並び替える)
  • +
  • ユーザの指定した順に行を並び替える
  • +
  • オンライン VACUUM FULL (行の詰め合わせを行う)
  • +
+

このユーティリティを使用するためには、以下のことに注意をしてください。

+
    +
  • このユーティリティは、スーパーユーザのみが実行することができます。
  • +
  • 対象のテーブルはPRIMARY KEYを持っている必要があります。
  • +
  • pg_reorg 実行後のデータの状態は、統計情報に反映されていません。統計情報を最新化するため、pg_reorg 実行後にANALYZEを実行してください。
  • +
+
+ +
+

+

testというデータベースをオンライン CLUSTER するには、下記のコマンドを実行します。

+
$ pg_reorg test
+

testという名前のデータベースのfooという1つのテーブルに対してオンライン VACUUM FULL を行うには、下記のコマンドを実行します。

+
$ pg_reorg --no-order --table foo -d test

+

+
+ +
+

オプション

+

pg_reorg では、下記の4種類のコマンドライン引数を指定できます。

+
+
+ +

connection-options

+

PostgreSQLに接続するためのパラメータです。

+ +
+
+
-h host
+--host host
+
サーバが稼働しているマシンのホスト名を指定します。ホスト名がスラッシュから始まる場合、Unixドメインソケット用のディレクトリとして使用されます。
+ +
-p port
+--port port
+
サーバが接続を監視するTCPポートもしくはUnixドメインソケットファイルの拡張子を指定します。
+ +
-U username
+--username username
+
接続するユーザ名を指定します。
+ +
-W
--password
+
データベースに接続する前に、pg_reorg は強制的にパスワード入力を促します。
+
サーバがパスワード認証を要求する場合 pg_reorg は自動的にパスワード入力を促しますので、これが重要になることはありません。 +しかし、pg_reorg は、サーバにパスワードが必要かどうかを判断するための接続試行を無駄に行います。 +こうした余計な接続試行を防ぐために-Wの入力が有意となる場合もあります。
+
+
+ +

message-options

+

+pg_reorg を実行した際に任意のメッセージを出力するためのパラメータです。 +--quietと他の2つのオプションを同時に指定した場合は、--quietのオプションは無視されます。 +

+ +
+
-e
--echo
+
pg_reorg が生成し、サーバに送るコマンドをエコー表示します。
+
-q
--quiet
+
進行メッセージを表示しません。
+
-v
--verbose
+
処理中に詳細な情報を表示します。
+
+ +

order-options

+

pg_reorg を実行する際の並び替えの基準を指定するパラメータです。 +何も指定されていない場合は、cluster index順にオンライン CLUSTER を行います。 +この2つを同時に指定することはできません。 +

+ +
+
-n
--no-order
+
オンライン VACUUM FULL の処理を行います。
+ +
-o columns [,...]
+--order-by columns [,...]
+
指定したカラムをキーにオンライン CLUSTER を行います。
+
+ +

target-options

+

+pg_reorg を実行する対象を指定するパラメータです。 +--all--dbnameまたは--tableを同時に指定することはできません。 +

+ +
+
-a
--all
+
対象となる全てのデータベースに対してオンライン CLUSTER、または、オンラインVACUUM FULLを行います。
+ +
+-d dbname
+--dbname dbname +
+
オンライン CLUSTER、または、オンライン VACUUM FULL を行うデータベース名を指定します。 +データベース名が指定されておらず、-a(または--all)も指定されていない場合、 +データベース名はPGDATABASE環境変数から読み取られます。この変数も設定されていない場合は、接続時に指定したユーザ名が使用されます。 +
+ +
+-t table
+--table table +
+
オンライン CLUSTER 、または、オンライン VACUUM FULL を行うテーブルを指定します。 +このオプションが指定されていない場合は、対象となったデータベースに存在する全ての対象テーブルに対して処理を行います。 +
+
+
+ +
+

環境変数

+
+
+ PGDATABASE
+ PGHOST
+ PGPORT
+ PGUSER +
+
デフォルトの接続パラメータです。
+
+
+

また、このユーティリティは、他のほとんどの PostgreSQL ユーティリティと同様、 + libpq でサポートされる環境変数を使用します。詳細については、環境変数の項目を参照してください。 +

+
+ +
+

トラブルシューティング

+

pg_reorg の実行に失敗した場合にエラーが表示されます。 +想像されるエラー原因と対処を示します。

+

致命的なエラーで終了した場合、手動によるクリーンアップを行う必要があります。 +クリーンアップは、エラーが発生したデータベースに対して、$PGHOME/share/contrib/uninstall_pg_reorg.sql を実行し、その後、$PGHOME/share/contrib/pg_reorg.sql を実行します。

+ +
+
pg_reorg : reorg database "template1" ... skipped
+
--allオプションを指定した際に、pg_reorg がインストールされていないデータベースに対して表示されます。
+
pg_reorg スキーマのインストールを行ってください。
+ +
ERROR: pg_reorg is not installed
+
--dbnameで指定したデータベースにpg_reorg がインストールされていません。
+
pg_reorg のインストールを行ってください。
+ +
ERROR: relation "table" has no primary key
+
指定したテーブルにPRIMARY KEYが存在していません。
+
対象のテーブルにPRIMARY KEYの作成を行ってください。(ALTER TABLE ADD PRIMARY KEY)
+ +
ERROR: relation "table" has no cluster key
+
指定したテーブルに CLUSTER KEYが存在していません。
+
対象のテーブルに CLUSTER KEYの作成を行ってください。(ALTER TABLE CLUSTER)
+ +
pg_reorg : query failed: ERROR: column "col" does not exist
+
--order-by で指定したカラムが対象のテーブルに存在していません。
+
対象のテーブルに存在するカラムを指定してください。
+ +
ERROR: permission denied for schema reorg
+
操作を行おうとした対象に権限がありません。
+
スーパーユーザで操作を行ってください。
+ +
pg_reorg : query failed: ERROR: trigger "z_reorg_trigger" for relation "tbl" already exists
+
操作を行おうとした対象にpg_reorg が処理のために作成するトリガと同名のものが存在しています。
+
トリガの改名か削除を行ってください。
+ +
pg_reorg : trigger conflicted for tbl
+
操作を行おうとした対象にpg_reorg が処理のために作成するトリガより後に実行されるトリガが存在しています。
+
トリガの改名か削除を行ってください。
+
+
+ +
+

制約

+

pg_reorg を使用する際には、以下の制約があります。以下の制約に関する操作を行った場合の動作は保証されません。注意してください。

+ +

一時テーブルへの操作

+

pg_reorg では、一時テーブルは操作の対象外です。

+ +

GiSTインデックスの使用

+

インデックス種別がGiSTとなっているインデックスがクラスタインデックスとなっている +テーブルはpg_reorg コマンドを使用して操作を行うことはできません。 +これは、GiSTインデックスのソート順序は一意ではないため、ORDER BYによる +ソートが行えないためです。

+ +

DDLコマンドの発行

+

+pg_reorg の実行中には、VACUUMANALYZE 以外 のDDL操作は行わないでください。 +多くの場合、pg_reorg は失敗しロールバックされます。 +しかし、以下の操作ではデータが破損するため、非常に危険です。 +

+ +
+
TRUNCATE
+
TRUNCATEにより削除した行が、pg_reorg 実行後には復元しています。操作結果が消失します。
+ +
CREATE INDEX
+
CREATE INDEXは、スワップされない索引が残る可能性があります。データの不整合が生じます。
+ +
ADD COLUMN
+
ADD COLUMNは、追加された値が全てNULLに置換されてしまう可能性があります。データが消失します。
+ +
ALTER COLUMN TYPE
+
ALTER COLUMN TYPEは、実行するとスキーマで定義された型と実際の格納状態に齟齬をきたします。データの不整合が生じます。
+ +
ALTER TABLE SET TABLESPACE
+
ALTER TABLE SET TABLE SPACEは、pg_reorg 実行後にrelfilenodeとの不整合が起こるため、対象のテーブルに対する参照/更新操作時にエラーが発生します。
+
+
+ +
+

インストール方法

+

pg_reorg のインストールは、標準のcontribモジュールと同様です。

+

ビルド

+

pg_reorg のフォルダを$PGHOME/contrib/に配置し、make, make installを行ってください。

+

データベースへの登録

+

PostgreSQLを起動し、対象のデータベースに対して $PGHOME/share/contrib にある pg_reorg.sql を実行し、インストールを行ってください。

+
+ +
+

動作環境

+
+
PostgreSQLバージョン
PostgreSQL 8.3
+
OS
RHEL 5.2, Windows XP SP3
+
ディスク容量
処理対象のテーブル、インデックスサイズの2倍以上のディスク空き容量
+
+
+ +
+

関連項目

+clusterdb, +vacuumdb +
+ +
+ +

+Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +

+ + + diff --git a/doc/pg_reorg.html b/doc/pg_reorg.html new file mode 100755 index 0000000..c8b1375 --- /dev/null +++ b/doc/pg_reorg.html @@ -0,0 +1,275 @@ + + + +pg_reorg + + + + + + +

pg_reorg

+ +
+

Name

+pg_reorg -- Reorganize tables in PostgreSQL databases without any locks. +
+ +
+

Synopsis

+

+pg_reorg [connection-options...] [message-options...] [order-options...] [target-options...] +

+

There 4 option categories. +See also options for details.

+
+
connection-options
+
-h [--host] host
+
-p [--port] port
+
-U [--username] username
+
-W [--password]
+
message-options
+
-e [--echo]
+
-q [--quiet]
+
-v [--verbose]
+
order-options
+
-o [--order-by] columns [,...]
+
-n [--no-order]
+
target-options
+
-a [--all]
+
-d [--dbname] dbname
+
-t [--table] table
+
+
+ +
+ +

Description

+

pg_reorg is an utility program to reorganize tables in PostgreSQL databases. +Unlike clusterdb, it doesn't block any selections and updates during reorganization. +You can choose one of the following methods to reorganize.

+
    +
  • Online CLUSTER (ordered by cluster index)
  • +
  • Ordered by specified columns
  • +
  • Online VACUUM FULL (packing rows only)
  • +
+

NOTICE:

+
    +
  • Only superusers can use the utility.
  • +
  • Target table must have PRIMARY KEY.
  • +
  • You'd better to do ANALYZE after pg_reorg is completed.
  • +
+
+ +
+

Examples

+

Execute the following command to do online CLUSTER to all tables in test database.

+
$ pg_reorg test
+

Execute the following command to do online VACUUM FULL to foo table in test database.

+
$ pg_reorg --no-order --table foo -d test

+

+ +
+

Options

+

pg_reorg has command line options in 4 categolies.

+
+
+ +

connection-options

+

Parameters to connect PostgreSQL.

+ +
+
+
-h host
+--host host
+
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.
+ +
-p port
+--port port
+
Specifies the TCP port or local Unix domain socket file extension on which the server is listening for connections.
+ +
-U username
+--username username
+
User name to connect as.
+ +
-W
--password
+
Force pg_reorg to prompt for a password before connecting to a database.
+
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 -W to avoid the extra connection attempt.
+
+
+ +

message-options

+

Specifies message output by pg_reorg. +--quiet is ignored if some of the other options are specified.

+ +
+
-e
--echo
+
Echo the commands that pg_reorg generates and sends to the server.
+
-q
--quiet
+
Do not display progress messages.
+
-v
--verbose
+
Print detailed information during processing.
+
+ +

order-options

+

Options to order rows. +If not specified, pg_reorg do online CLUSTER using cluster indexes. +Only one option can be specified. +

+ +
+
-n
--no-order
+
Do online VACUUM FULL.
+ +
-o columns [,...]
+--order-by columns [,...]
+
Do online CLUSTER ordered by specified columns.
+
+ + +

target-options

+

+Options to specify target tables or databases. +You cannot use --all and --dbname or --table together. +

+ +
+
-a
--all
+
Reorganize all databases.
+ +
+-d dbname
+--dbname dbname +
+
Specifies the name of the database to be reorganized. If this is not specified and -a (or --all) is not used, the database name is read from the environment variable PGDATABASE. If that is not set, the user name specified for the connection is used.
+ +
+-t table
+--table table +
+
Reorganize table only. If you don't specify this option, all tables in specified databases are reorganized.
+
+
+ +
+

Environment

+
+
+ PGDATABASE
+ PGHOST
+ PGPORT
+ PGUSER +
+
Default connection parameters
+
+
+

This utility, like most other PostgreSQL utilities, also uses the environment variables supported by libpq (see Environment Variables).

+
+ +
+

Diagnostics

+

Error messages are reported when pg_reorg fails. +The following list shows the cause of errors.

+

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.)

+ +
+
pg_reorg : reorg database "template1" ... skipped
+
pg_reorg is not installed in the database when --all option is specified.
+
Do register pg_reorg to the database.
+ +
ERROR: pg_reorg is not installed
+
pg_reorg is not installed in the database specified by --dbname.
+
Do register pg_reorg to the database.
+ +
ERROR: relation "table" has no primary key
+
The target table doesn't have PRIMARY KEY.
+
Define PRIMARY KEY to the table. (ALTER TABLE ADD PRIMARY KEY)
+ +
ERROR: relation "table" has no cluster key
+
The target table doesn't have CLUSTER KEY.
+
Define CLUSTER KEY to the table. (ALTER TABLE CLUSTER)
+ +
pg_reorg : query failed: ERROR: column "col" does not exist
+
The target table doesn't have columns specified by --order-by option.
+
Specify existing columns.
+ +
ERROR: permission denied for schema reorg
+
Permission error.
+
pg_reorg must be executed by superusers.
+ +
pg_reorg : query failed: ERROR: trigger "z_reorg_trigger" for relation "tbl" already exists
+
The target table already has a trigger named "z_reorg_trigger".
+
Delete or rename the trigger.
+ +
pg_reorg : trigger conflicted for tbl
+
The target table already has a trigger which follows by "z_reorg_trigger" in alphabetical order.
+
Delete or rename the trigger.
+
+
+ +
+

Restrictions

+

pg_reorg has the following restrictions. +Be careful to avoid data corruptions.

+ +

Temp tables

+

pg_reorg cannot reorganize temp tables.

+ +

GiST indexes

+

pg_reorg cannot reorganize tables using GiST indexes.

+ +

DDL commands

+

You cannot do DDL commands except VACUUM and ANALYZE during pg_reorg. +In many case pg_reorg would fail and rollback collectly, but there are some cases ending with data-corruption .

+ +
+
TRUNCATE
+
TRUNCATE is lost. Deleted rows still exist after pg_reorg.
+ +
CREATE INDEX
+
CREATE INDEX causes index corruptions.
+ +
ADD COLUMN
+
ADD COLUMN causes lost of data. Newly added columns are initialized with NULLs.
+ +
ALTER COLUMN TYPE
+
ALTER COLUMN TYPE cases data coruuptions.
+ +
ALTER TABLE SET TABLESPACE
+
ALTER TABLE SET TABLE SPACE cases data corruptions by wrong relfilenode.
+
+
+ +
+

Installations

+

pg_reorg can be installed like standard contrib modules.

+

Build from source

+

Place pg_reorg to $PGHOME/contrib/ and input make, make install.

+

Register to database

+

Start PostgreSQL and execute pg_reorg.sql in $PGHOME/share/contrib.

+
+ +
+

Requirements

+
+
PostgreSQL version
PostgreSQL 8.3
+
OS
RHEL 5.2, Windows XP SP3
+
Disks
Requires amount of disks twice larger than target table and indexes.
+
+
+ +
+

See Also

+clusterdb, +vacuumdb +
+ +
+ +

+Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION +

+ + + diff --git a/doc/style.css b/doc/style.css new file mode 100755 index 0000000..c3107b4 --- /dev/null +++ b/doc/style.css @@ -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; +} diff --git a/lib/Makefile b/lib/Makefile new file mode 100755 index 0000000..f4b6300 --- /dev/null +++ b/lib/Makefile @@ -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 diff --git a/lib/pg_reorg.sql.in b/lib/pg_reorg.sql.in new file mode 100755 index 0000000..82e1871 --- /dev/null +++ b/lib/pg_reorg.sql.in @@ -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; diff --git a/lib/reorg.c b/lib/reorg.c new file mode 100755 index 0000000..7a18c02 --- /dev/null +++ b/lib/reorg.c @@ -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); +} diff --git a/lib/uninstall_pg_reorg.sql b/lib/uninstall_pg_reorg.sql new file mode 100755 index 0000000..e4eb542 --- /dev/null +++ b/lib/uninstall_pg_reorg.sql @@ -0,0 +1,7 @@ +/* + * pg_reorg: lib/uninstall_reorg.sql + * + * Copyright (c) 2008, NIPPON TELEGRAPH AND TELEPHONE CORPORATION + */ + +DROP SCHEMA IF EXISTS reorg CASCADE;