Added --namespace option to set the namespace of repacked tables

Bumped version number to enforce extension re-creation as the SQL has
been modified.

Current limitations:

- Check for namespace existence: on error temp objects are left around
- What happens to the indexes?
- Tests needed.
- Should the default be the GUC default_tablespace instead of pg_default?
  This is actually an original pg_repack shortcoming, not a regression.
This commit is contained in:
Daniele Varrazzo 2013-02-21 17:10:12 +00:00
parent 89031f9cc5
commit 6710e514db
3 changed files with 62 additions and 26 deletions

View File

@ -2,7 +2,7 @@
"name": "pg_repack", "name": "pg_repack",
"abstract": "PostgreSQL module for data reorganization", "abstract": "PostgreSQL module for data reorganization",
"description": "Reorganize tables in PostgreSQL databases with minimal locks", "description": "Reorganize tables in PostgreSQL databases with minimal locks",
"version": "1.2dev0", "version": "1.2dev1",
"maintainer": [ "maintainer": [
"Josh Kupershmidt <schmiddy@gmail.com>", "Josh Kupershmidt <schmiddy@gmail.com>",
"Daniele Varrazzo <daniele.varrazzo@gmail.com>" "Daniele Varrazzo <daniele.varrazzo@gmail.com>"
@ -13,7 +13,7 @@
"provides": { "provides": {
"pg_repack": { "pg_repack": {
"file": "lib/pg_repack.sql", "file": "lib/pg_repack.sql",
"version": "1.2dev0", "version": "1.2dev1",
"abstract": "Reorganize tables in PostgreSQL databases with minimal locks" "abstract": "Reorganize tables in PostgreSQL databases with minimal locks"
} }
}, },

View File

@ -197,6 +197,7 @@ static bool alldb = false;
static bool noorder = false; static bool noorder = false;
static SimpleStringList table_list = {NULL, NULL}; static SimpleStringList table_list = {NULL, NULL};
static char *orderby = NULL; static char *orderby = NULL;
static char *tablespace = NULL;
static int wait_timeout = 60; /* in seconds */ static int wait_timeout = 60; /* in seconds */
static int jobs = 0; /* number of concurrent worker conns. */ static int jobs = 0; /* number of concurrent worker conns. */
@ -214,6 +215,7 @@ static pgut_option options[] =
{ 'l', 't', "table", &table_list }, { 'l', 't', "table", &table_list },
{ 'b', 'n', "no-order", &noorder }, { 'b', 'n', "no-order", &noorder },
{ 's', 'o', "order-by", &orderby }, { 's', 'o', "order-by", &orderby },
{ 's', 's', "tablespace", &tablespace },
{ 'i', 'T', "wait-timeout", &wait_timeout }, { 'i', 'T', "wait-timeout", &wait_timeout },
{ 'B', 'Z', "no-analyze", &analyze }, { 'B', 'Z', "no-analyze", &analyze },
{ 'i', 'j', "jobs", &jobs }, { 'i', 'j', "jobs", &jobs },
@ -360,10 +362,15 @@ repack_one_database(const char *orderby, char *errbuf, size_t errsize)
StringInfoData sql; StringInfoData sql;
SimpleStringListCell *cell; SimpleStringListCell *cell;
const char **params = NULL; const char **params = NULL;
size_t num_params = simple_string_list_size(table_list); int iparam = 0;
size_t num_tables;
size_t num_params;
if (num_params) num_tables = simple_string_list_size(table_list);
params = pgut_malloc(num_params * sizeof(char *));
/* 1st param is the user-specified tablespace */
num_params = num_tables + 1;
params = pgut_malloc(num_params * sizeof(char *));
initStringInfo(&sql); initStringInfo(&sql);
@ -442,29 +449,46 @@ repack_one_database(const char *orderby, char *errbuf, size_t errsize)
command("SET client_min_messages = warning", 0, NULL); command("SET client_min_messages = warning", 0, NULL);
/* acquire target tables */ /* acquire target tables */
appendStringInfoString(&sql, "SELECT * FROM repack.tables WHERE "); appendStringInfoString(&sql,
if (num_params) "SELECT t.*,"
" coalesce(v.tablespace, t.tablespace_orig) as tablespace_dest"
" FROM repack.tables t, "
" (VALUES (quote_ident($1::text))) as v (tablespace)"
" WHERE ");
params[iparam++] = tablespace;
if (num_tables)
{ {
appendStringInfoString(&sql, "("); appendStringInfoString(&sql, "(");
for (i = 0, cell = table_list.head; cell; cell = cell->next, i++) for (cell = table_list.head; cell; cell = cell->next)
{ {
/* Construct table name placeholders to be used by PQexecParams */ /* Construct table name placeholders to be used by PQexecParams */
appendStringInfo(&sql, "relid = $%d::regclass", i + 1); appendStringInfo(&sql, "relid = $%d::regclass", iparam + 1);
params[i] = cell->val; params[iparam++] = cell->val;
if (cell->next) if (cell->next)
appendStringInfoString(&sql, " OR "); appendStringInfoString(&sql, " OR ");
} }
appendStringInfoString(&sql, ")"); appendStringInfoString(&sql, ")");
res = execute_elevel(sql.data, (int) num_params, params, DEBUG2);
} }
else else
{ {
appendStringInfoString(&sql, "pkid IS NOT NULL"); appendStringInfoString(&sql, "pkid IS NOT NULL");
if (!orderby) if (!orderby)
appendStringInfoString(&sql, " AND ckid IS NOT NULL"); appendStringInfoString(&sql, " AND ckid IS NOT NULL");
res = execute_elevel(sql.data, 0, NULL, DEBUG2);
} }
/* double check the parameters array is sane */
if (iparam != num_params)
{
if (errbuf)
snprintf(errbuf, errsize,
"internal error: bad parameters count: %i instead of %zi",
iparam, num_params);
goto cleanup;
}
res = execute_elevel(sql.data, (int) num_params, params, DEBUG2);
/* on error skip the database */ /* on error skip the database */
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
{ {
@ -489,7 +513,9 @@ repack_one_database(const char *orderby, char *errbuf, size_t errsize)
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
{ {
repack_table table; repack_table table;
const char *create_table; const char *create_table_1;
const char *create_table_2;
const char *tablespace;
const char *ckey; const char *ckey;
int c = 0; int c = 0;
@ -512,13 +538,24 @@ repack_one_database(const char *orderby, char *errbuf, size_t errsize)
table.create_trigger = getstr(res, i, c++); table.create_trigger = getstr(res, i, c++);
table.enable_trigger = getstr(res, i, c++); table.enable_trigger = getstr(res, i, c++);
create_table = getstr(res, i, c++); create_table_1 = getstr(res, i, c++);
tablespace = getstr(res, i, c++); /* to be clobbered */
create_table_2 = getstr(res, i, c++);
table.drop_columns = getstr(res, i, c++); table.drop_columns = getstr(res, i, c++);
table.delete_log = getstr(res, i, c++); table.delete_log = getstr(res, i, c++);
table.lock_table = getstr(res, i, c++); table.lock_table = getstr(res, i, c++);
ckey = getstr(res, i, c++); ckey = getstr(res, i, c++);
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++);
tablespace = getstr(res, i, c++);
resetStringInfo(&sql); resetStringInfo(&sql);
appendStringInfoString(&sql, create_table_1);
appendStringInfoString(&sql, tablespace);
appendStringInfoString(&sql, create_table_2);
if (!orderby) if (!orderby)
{ {
/* CLUSTER mode */ /* CLUSTER mode */
@ -529,27 +566,23 @@ repack_one_database(const char *orderby, char *errbuf, size_t errsize)
errmsg("relation \"%s\" has no cluster key", table.target_name))); errmsg("relation \"%s\" has no cluster key", table.target_name)));
continue; continue;
} }
appendStringInfo(&sql, "%s ORDER BY %s", create_table, ckey); appendStringInfoString(&sql, " ORDER BY ");
table.create_table = sql.data; appendStringInfoString(&sql, ckey);
table.create_table = sql.data;
} }
else if (!orderby[0]) else if (!orderby[0])
{ {
/* VACUUM FULL mode */ /* VACUUM FULL mode */
table.create_table = create_table; table.create_table = sql.data;
} }
else else
{ {
/* User specified ORDER BY */ /* User specified ORDER BY */
appendStringInfo(&sql, "%s ORDER BY %s", create_table, orderby); appendStringInfoString(&sql, " ORDER BY ");
table.create_table = sql.data; appendStringInfoString(&sql, 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++);
repack_one_table(&table, orderby); repack_one_table(&table, orderby);
} }
ret = true; ret = true;
@ -1430,6 +1463,7 @@ pgut_help(bool details)
printf(" -n, --no-order do vacuum full instead of cluster\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(" -o, --order-by=COLUMNS order by columns instead of cluster keys\n");
printf(" -t, --table=TABLE repack specific table only\n"); printf(" -t, --table=TABLE repack specific table only\n");
printf(" -s, --tablespace=TABLESPC move repacked tables to a new tablespace\n");
printf(" -T, --wait-timeout=SECS timeout to cancel other backends on conflict\n"); printf(" -T, --wait-timeout=SECS timeout to cancel other backends on conflict\n");
printf(" -Z, --no-analyze don't analyze at end\n"); printf(" -Z, --no-analyze don't analyze at end\n");
} }

View File

@ -179,7 +179,9 @@ CREATE VIEW repack.tables AS
'CREATE TABLE repack.log_' || R.oid || ' (id bigserial PRIMARY KEY, pk repack.pk_' || R.oid || ', row ' || repack.oid2text(R.oid) || ')' AS create_log, 'CREATE TABLE repack.log_' || R.oid || ' (id bigserial PRIMARY KEY, pk repack.pk_' || R.oid || ', row ' || repack.oid2text(R.oid) || ')' AS create_log,
repack.get_create_trigger(R.oid, PK.indexrelid) AS create_trigger, repack.get_create_trigger(R.oid, PK.indexrelid) AS create_trigger,
repack.get_enable_trigger(R.oid) as enable_trigger, repack.get_enable_trigger(R.oid) as enable_trigger,
'CREATE TABLE repack.table_' || R.oid || ' WITH (' || array_to_string(array_append(R.reloptions, 'oids=' || CASE WHEN R.relhasoids THEN 'true' ELSE 'false' END), ',') || ') TABLESPACE ' || coalesce(quote_ident(S.spcname), 'pg_default') || ' AS SELECT ' || repack.get_columns_for_create_as(R.oid) || ' FROM ONLY ' || repack.oid2text(R.oid) AS create_table, 'CREATE TABLE repack.table_' || R.oid || ' WITH (' || array_to_string(array_append(R.reloptions, 'oids=' || CASE WHEN R.relhasoids THEN 'true' ELSE 'false' END), ',') || ') TABLESPACE ' AS create_table_1,
coalesce(quote_ident(S.spcname), 'pg_default') tablespace_orig,
' AS SELECT ' || repack.get_columns_for_create_as(R.oid) || ' FROM ONLY ' || repack.oid2text(R.oid) AS create_table_2,
repack.get_drop_columns(R.oid, 'repack.table_' || R.oid) AS drop_columns, repack.get_drop_columns(R.oid, 'repack.table_' || R.oid) AS drop_columns,
'DELETE FROM repack.log_' || R.oid AS delete_log, 'DELETE FROM repack.log_' || R.oid AS delete_log,
'LOCK TABLE ' || repack.oid2text(R.oid) || ' IN ACCESS EXCLUSIVE MODE' AS lock_table, 'LOCK TABLE ' || repack.oid2text(R.oid) || ' IN ACCESS EXCLUSIVE MODE' AS lock_table,