Fixed database corruption when target tables have dropped columns, and

there are views or functions depending on columns after dropped ones.
The issue was reported by depesz, and original patch by Denish Patel.

Improved documentation how to build binaries from source.

COPYRIGHT updated.
This commit is contained in:
Takahiro Itagaki
2011-04-29 05:06:48 +00:00
parent 830ef422ad
commit 960930b645
24 changed files with 198 additions and 177 deletions

View File

@ -1,7 +1,8 @@
#
# pg_reorg: lib/Makefile
#
# Copyright (c) 2008-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
# Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
# Portions Copyright (c) 2011, Itagaki Takahiro
#
SRCS = reorg.c pgut/pgut-be.c pgut/pgut-spi.c
OBJS = $(SRCS:.c=.o)

View File

@ -1,7 +1,8 @@
/*
* pg_reorg: lib/pg_reorg.sql.in
*
* Copyright (c) 2008-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*/
-- Adjust this setting to control where the objects get created.
@ -106,6 +107,43 @@ $$
$$
LANGUAGE sql STABLE STRICT;
-- Get a column list for SELECT all columns including dropped ones.
-- We use NULLs of integer types for dropped columns (types are not important).
CREATE FUNCTION reorg.get_columns_for_create_as(oid)
RETURNS text AS
$$
SELECT array_to_string(reorg.array_accum(c), ',') FROM (SELECT
CASE WHEN attisdropped
THEN 'NULL::integer AS ' || quote_ident(attname)
ELSE quote_ident(attname)
END AS c
FROM pg_attribute
WHERE attrelid = $1 AND attnum > 0 ORDER BY attnum
) AS COL
$$
LANGUAGE sql STABLE STRICT;
-- Get a SQL text to DROP dropped columns for the table,
-- or NULL if it has no dropped columns.
CREATE FUNCTION reorg.get_drop_columns(oid, text)
RETURNS text AS
$$
SELECT
'ALTER TABLE ' || $2 || ' ' || array_to_string(dropped_columns, ', ')
FROM (
SELECT
reorg.array_accum('DROP COLUMN ' || quote_ident(attname)) AS dropped_columns
FROM (
SELECT * FROM pg_attribute
WHERE attrelid = $1 AND attnum > 0 AND attisdropped
ORDER BY attnum
) T
) T
WHERE
array_upper(dropped_columns, 1) > 0
$$
LANGUAGE sql STABLE STRICT;
-- includes not only PRIMARY KEYS but also UNIQUE NOT NULL keys
CREATE VIEW reorg.primary_keys AS
SELECT indrelid, (reorg.array_accum(indexrelid))[1] AS indexrelid
@ -130,7 +168,8 @@ CREATE VIEW reorg.tables AS
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 ' || reorg.oid2text(R.oid) || ')' 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=' || CASE WHEN R.relhasoids THEN 'true' ELSE 'false' END), ',') || ') TABLESPACE ' || coalesce(quote_ident(S.spcname), 'pg_default') || ' AS SELECT * FROM ONLY ' || reorg.oid2text(R.oid) AS create_table,
'CREATE TABLE reorg.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 ' || reorg.get_columns_for_create_as(R.oid) || ' FROM ONLY ' || reorg.oid2text(R.oid) AS create_table,
reorg.get_drop_columns(R.oid, 'reorg.table_' || R.oid) AS drop_columns,
'DELETE FROM reorg.log_' || R.oid AS delete_log,
'LOCK TABLE ' || reorg.oid2text(R.oid) || ' IN ACCESS EXCLUSIVE MODE' AS lock_table,
reorg.get_index_keys(CK.indexrelid, R.oid) AS ckey,

View File

@ -1,9 +1,8 @@
/*-------------------------------------------------------------------------
*
* pgut-be.c
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*-------------------------------------------------------------------------
*/

View File

@ -1,9 +1,8 @@
/*-------------------------------------------------------------------------
*
* pgut-be.h
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*-------------------------------------------------------------------------
*/

View File

@ -1,9 +1,8 @@
/*-------------------------------------------------------------------------
*
* pgut-spi.c
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*-------------------------------------------------------------------------
*/

View File

@ -1,9 +1,8 @@
/*-------------------------------------------------------------------------
*
* pgut-spi.h
*
* Copyright (c) 2009-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
*
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*-------------------------------------------------------------------------
*/

View File

@ -1,7 +1,8 @@
/*
* pg_reorg: lib/reorg.c
*
* Copyright (c) 2008-2010, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2008-2011, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 2011, Itagaki Takahiro
*/
#include "postgres.h"
@ -75,7 +76,7 @@ static void RenameRelationInternal(Oid myrelid, const char *newrelname, Oid name
Datum
reorg_version(PG_FUNCTION_ARGS)
{
return CStringGetTextDatum("pg_reorg 1.1.5");
return CStringGetTextDatum("pg_reorg 1.1.6");
}
/**
@ -592,101 +593,6 @@ getint16(HeapTuple tuple, TupleDesc desc, int column)
return isnull ? 0 : DatumGetInt16(datum);
}
static void
remove_dropped_columns_and_adjust_attnum(Oid oid, int16 natts1, int16 natts2)
{
/* delete dropped columns */
execute_with_format(SPI_OK_DELETE,
"DELETE FROM pg_catalog.pg_attribute"
" WHERE attrelid = %u AND attisdropped",
oid);
if (SPI_processed != natts1 - natts2)
elog(ERROR, "cannot remove %d dropped columns (%u columns removed)",
natts2 - natts1, SPI_processed);
/* renumber attnum */
#if PG_VERSION_NUM >= 90000
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_attribute m"
" SET attnum = (SELECT count(*) FROM pg_attribute a"
" WHERE m.attrelid = a.attrelid"
" AND m.attnum >= a.attnum"
" AND a.attnum > 0 AND NOT a.attisdropped)"
" WHERE attrelid = %u AND attnum > 0 AND NOT attisdropped",
oid);
if (SPI_processed != natts2)
elog(ERROR, "cannot update %d columns (%u columns updated)",
natts2, SPI_processed);
#elif PG_VERSION_NUM >= 80300
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_attribute"
" SET attnum = (SELECT count(*) FROM pg_attribute a"
" WHERE pg_catalog.pg_attribute.attrelid = a.attrelid"
" AND pg_catalog.pg_attribute.attnum >= a.attnum"
" AND a.attnum > 0 AND NOT a.attisdropped)"
" WHERE attrelid = %u AND attnum > 0 AND NOT attisdropped",
oid);
if (SPI_processed != natts2)
elog(ERROR, "cannot update %d columns (%u columns updated)",
natts2, SPI_processed);
#else
/*
* Use count(*) in subquery because 8.2 doesn't support aggregates
* in UPDATE SET.
*/
do
{
uint32 i;
uint32 ntuples;
SPITupleTable *tuptable;
TupleDesc desc;
execute_with_format(SPI_OK_SELECT,
"SELECT attnum FROM pg_catalog.pg_attribute"
" WHERE attrelid = %u AND attnum > 0 AND NOT attisdropped"
" ORDER BY attnum",
oid);
if (SPI_processed != natts2)
elog(ERROR, "number of columns should be %d (%d returned)",
natts2, SPI_processed);
ntuples = SPI_processed;
tuptable = SPI_tuptable;
desc = tuptable->tupdesc;
for (i = 0; i < ntuples; i++)
{
int attnum;
int count;
attnum = getint16(tuptable->vals[i], desc, 1);
execute_with_format(SPI_OK_SELECT,
"SELECT count(*)::smallint FROM pg_catalog.pg_attribute"
" WHERE attrelid = %u AND attnum > 0 AND attnum <= %d",
oid, attnum);
if (SPI_processed != 1)
elog(ERROR, "cannot adjust column %d", attnum);
count = getint16(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_attribute"
" SET attnum = %d"
" WHERE attrelid = %u AND attnum = %d",
count, oid, attnum);
if (SPI_processed != 1)
elog(ERROR, "cannot update column %d", attnum);
}
} while(0);
#endif
/* adjust attribute number of the table */
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_class SET relnatts = %d WHERE oid = %u",
natts2, oid);
}
/**
* @fn Datum reorg_swap(PG_FUNCTION_ARGS)
* @brief Swapping relfilenode of tables and relation ids of toast tables
@ -799,27 +705,6 @@ reorg_swap(PG_FUNCTION_ARGS)
idx2 = getoid(tuple, desc, 2);
swap_heap_or_index_files(idx1, idx2);
/* adjust key attnum if the target table has dropped columns */
if (natts1 != natts2)
{
#if PG_VERSION_NUM >= 90000
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_index m SET indkey = n.indkey"
" FROM pg_catalog.pg_index n"
" WHERE m.indexrelid = %u"
" AND n.indexrelid = 'reorg.index_%u'::regclass",
idx1, idx1);
#else
execute_with_format(SPI_OK_UPDATE,
"UPDATE pg_catalog.pg_index SET indkey = n.indkey"
" FROM pg_catalog.pg_index n"
" WHERE pg_catalog.pg_index.indexrelid = %u"
" AND n.indexrelid = 'reorg.index_%u'::regclass",
idx1, idx1);
#endif
if (SPI_processed != 1)
elog(ERROR, "failed to update pg_index.indkey (%u rows updated)", SPI_processed);
}
CommandCounterIncrement();
}
@ -876,10 +761,6 @@ reorg_swap(PG_FUNCTION_ARGS)
CommandCounterIncrement();
}
/* adjust attribute numbers if the target table has dropped columns */
if (natts1 != natts2)
remove_dropped_columns_and_adjust_attnum(oid, natts1, natts2);
/* drop reorg trigger */
execute_with_format(
SPI_OK_UTILITY,

View File

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