-- -- PostgreSQL Partitioning Utility -- -- Copyright(C) 2012 Uptime Technologies, LLC. -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation; version 2 of the License. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License along -- with this program; if not, write to the Free Software Foundation, Inc., -- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -- CREATE SCHEMA pgpart; BEGIN; -- -- pgpart._get_attname_by_attnum() -- -- Get an attribute name by nspname, relname and attribute number -- CREATE OR REPLACE FUNCTION pgpart._get_attname_by_attnum ( NAME, NAME, SMALLINT ) RETURNS NAME AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _attnum ALIAS FOR $3; _attname NAME; BEGIN SELECT a.attname INTO _attname FROM pg_namespace n, pg_class c, pg_attribute a WHERE n.nspname = _nspname AND c.relname = _relname AND n.oid = c.relnamespace AND c.oid = a.attrelid AND a.attnum = _attnum; RETURN _attname; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_primary_key_def() -- -- Get a primary key definition string for new partition. -- CREATE OR REPLACE FUNCTION pgpart._get_primary_key_def ( NAME, NAME, NAME ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _conname NAME; _conkey SMALLINT[]; _size SMALLINT; _keyidx SMALLINT; _keyname NAME; _keys TEXT; BEGIN SELECT a.conname, a.conkey, array_length(a.conkey, 1) INTO _conname, _conkey, _size FROM pg_namespace n, pg_class c, pg_constraint a WHERE n.nspname = _nspname AND c.relname = _relname AND n.oid = c.relnamespace AND c.oid = a.conrelid AND a.contype = ''p''; IF NOT FOUND THEN RETURN ''''; END IF; _keys = ''''; FOR _keyidx IN 1.._size LOOP SELECT pgpart._get_attname_by_attnum(_nspname::name, _relname::name, _keyidx::smallint) INTO _keyname; _keys = _keys || '','' || _keyname; END LOOP; RETURN ''ALTER TABLE '' || _nspname || ''.'' || _partname || '' ADD PRIMARY KEY ('' || substring(_keys,2) || '')''; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_index_def() -- -- Get index definition string(s) for new partition, excepting primary key. -- CREATE OR REPLACE FUNCTION pgpart._get_index_def ( NAME, NAME, NAME ) RETURNS SETOF TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _r RECORD; _indexname NAME; _indexdef TEXT; BEGIN FOR _r IN SELECT indexdef, replace(regexp_replace(regexp_replace(indexdef, ''.*\('', ''''), ''\).*'', ''''), '', '', ''_'') AS colname FROM pg_indexes WHERE schemaname = _nspname AND tablename = _relname AND indexname IN ( SELECT c2.relname FROM pg_namespace n, pg_class c, pg_index i, pg_class c2 WHERE n.nspname = _nspname AND c.relname = _relname AND n.oid = c.relnamespace AND c.oid = i.indrelid AND i.indisprimary <> true AND i.indexrelid = c2.oid ) LOOP _indexname = _partname || ''_'' || _r.colname || ''_idx''; _indexdef = _r.indexdef; _indexdef = regexp_replace(_indexdef, ''INDEX .* ON '', ''INDEX '' || _indexname || '' ON ''); _indexdef = replace(_indexdef, '' ON '' || _relname, '' ON '' || _nspname || ''.'' || _partname); RETURN NEXT _indexdef; END LOOP; RETURN; END ' LANGUAGE 'plpgsql'; -- -- pgpart._get_partition_def() -- -- Get a partiton definition string for new partition -- CREATE OR REPLACE FUNCTION pgpart._get_partition_def ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _cond ALIAS FOR $4; _partition_def TEXT; _const_def TEXT; BEGIN _const_def = pgpart._get_constraint_def(_partname, _cond); _partition_def = ''CREATE TABLE '' || _nspname || ''.'' || _partname || ''( ''; _partition_def = _partition_def || ''CONSTRAINT '' || _const_def; _partition_def = _partition_def || '') INHERITS ('' || _nspname || ''.'' || _relname || '')''; RETURN _partition_def; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_export_query() -- -- Get a query to export records with specified condition from parent table. -- CREATE OR REPLACE FUNCTION pgpart._get_export_query ( NAME, NAME, TEXT, TEXT ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _cond ALIAS FOR $3; _temp_file ALIAS FOR $4; _query TEXT; BEGIN _query = ''COPY ( '' || ''SELECT * FROM '' || _nspname || ''.'' || _relname || '' WHERE '' || _cond || '' ) to '''''' || _temp_file || ''''''''; RETURN _query; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_import_query() -- -- Get a query to import records into specified partition -- CREATE OR REPLACE FUNCTION pgpart._get_import_query ( NAME, NAME, TEXT ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _temp_file ALIAS FOR $3; _query TEXT; BEGIN _query = ''COPY '' || _nspname || ''.'' || _relname || '' FROM '''''' || _temp_file || ''''''''; RETURN _query; END; ' LANGUAGE 'plpgsql'; -- -- pgpart.add_partition() -- -- Add a new partition with a specified condition -- CREATE OR REPLACE FUNCTION pgpart.add_partition ( NAME, NAME, NAME, TEXT, TEXT ) RETURNS BOOLEAN AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _cond ALIAS FOR $4; _temp_file ALIAS FOR $5; _r RECORD; _def TEXT; BEGIN FOR _r IN SELECT pgpart._get_partition_def(_nspname, _relname, _partname, _cond) LOOP _def = _r._get_partition_def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; END LOOP; SELECT pgpart._get_export_query(_nspname, _relname, _cond, _temp_file) INTO _def; _def = _def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; _def = ''DELETE FROM '' || _nspname || ''.'' || _relname || '' WHERE '' || _cond; _def = _def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; SELECT pgpart._get_import_query(_nspname, _partname, _temp_file) INTO _def; _def = _def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; SELECT pgpart._get_primary_key_def(_nspname, _relname, _partname) INTO _def; _def = _def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; FOR _r IN SELECT pgpart._get_index_def(_nspname, _relname, _partname) LOOP _def = _r._get_index_def || '';''; RAISE NOTICE ''add_partition: %'', _def; EXECUTE _def; END LOOP; RETURN true; END ' LANGUAGE 'plpgsql'; -- -- pgpart.merge_partition() -- -- Merge a partition into the parent table. -- CREATE OR REPLACE FUNCTION pgpart.merge_partition ( NAME, NAME, NAME, TEXT, TEXT ) RETURNS BOOLEAN AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _cond ALIAS FOR $4; _temp_file ALIAS FOR $5; _r RECORD; _def TEXT; BEGIN SELECT pgpart._get_export_query(_nspname, _partname, ''1 = 1'', _temp_file) INTO _def; _def = _def || '';''; RAISE NOTICE ''merge_partition: %'', _def; EXECUTE _def; SELECT pgpart._get_import_query(_nspname, _relname, _temp_file) INTO _def; _def = _def || '';''; RAISE NOTICE ''merge_partition: %'', _def; EXECUTE _def; SELECT pgpart._get_detach_partition_def(_nspname, _relname, _partname) INTO _def; _def = _def || '';''; RAISE NOTICE ''merge_partition: %'', _def; EXECUTE _def; _def = ''DROP TABLE '' || _nspname || ''.'' || _partname; _def = _def || '';''; RAISE NOTICE ''merge_partition: %'', _def; EXECUTE _def; RETURN true; END ' LANGUAGE 'plpgsql'; -- -- pgpart._get_constraint_name() -- CREATE OR REPLACE FUNCTION pgpart._get_constraint_name ( NAME ) RETURNS TEXT AS ' DECLARE _partname ALIAS FOR $1; BEGIN RETURN ''__'' || _partname || ''_check''; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_constraint_def() -- CREATE OR REPLACE FUNCTION pgpart._get_constraint_def ( NAME, TEXT ) RETURNS TEXT AS ' DECLARE _partname ALIAS FOR $1; _cond ALIAS FOR $2; BEGIN RETURN pgpart._get_constraint_name(_partname) || '' CHECK('' || _cond || '')''; END; ' LANGUAGE 'plpgsql'; -- -- pgpart._get_attach_partition_def() -- -- Get a definition string for attaching a partition -- CREATE OR REPLACE FUNCTION pgpart._get_attach_partition_def ( NAME, NAME, NAME, TEXT ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _cond ALIAS FOR $4; _partition_def TEXT; _const_def TEXT; BEGIN _const_def = pgpart._get_constraint_def(_partname, _cond); _partition_def = ''ALTER TABLE '' || _nspname || ''.'' || _partname; _partition_def = _partition_def || '' INHERIT '' || _relname || '',''; _partition_def = _partition_def || '' ADD CONSTRAINT '' || _const_def; RETURN _partition_def; END; ' LANGUAGE 'plpgsql'; -- -- pgpart.attach_partition() -- -- Attach a new partition to the parent table with a specified condition -- CREATE OR REPLACE FUNCTION pgpart.attach_partition ( NAME, NAME, NAME, TEXT ) RETURNS BOOLEAN AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _cond ALIAS FOR $4; _r RECORD; _def TEXT; BEGIN -- -- Check whether constraint is valid for this partition. -- _def = ''SELECT count(*) FROM '' || _nspname || ''.'' || _partname || '' WHERE NOT ('' || _cond || '')''; RAISE NOTICE ''attach_partition: %'', _def; FOR _r IN EXECUTE _def LOOP IF _r.count > 0 THEN RAISE EXCEPTION ''attach_partition: % record(s) in this partition does not satisfy specified constraint.'', _r.count; END IF; END LOOP; FOR _r IN SELECT pgpart._get_attach_partition_def(_nspname, _relname, _partname, _cond) LOOP _def = _r._get_attach_partition_def || '';''; RAISE NOTICE ''attach_partition: %'', _def; EXECUTE _def; END LOOP; -- SELECT pgpart._get_primary_key_def(_nspname, _relname, _partname) -- INTO _def; -- _def = _def || '';''; -- RAISE NOTICE ''attach_partition: %'', _def; -- EXECUTE _def; -- -- FOR _r IN SELECT pgpart._get_index_def(_nspname, _relname, _partname) LOOP -- _def = _r._get_index_def || '';''; -- RAISE NOTICE ''attach_partition: %'', _def; -- EXECUTE _def; -- END LOOP; RETURN true; END ' LANGUAGE 'plpgsql'; -- -- pgpart._get_detach_partition_def() -- -- Get a definition string for detaching a partition -- CREATE OR REPLACE FUNCTION pgpart._get_detach_partition_def ( NAME, NAME, NAME ) RETURNS TEXT AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _partition_def TEXT; _const_name TEXT; BEGIN _const_name = pgpart._get_constraint_name(_partname); _partition_def = ''ALTER TABLE '' || _nspname || ''.'' || _partname; _partition_def = _partition_def || '' NO INHERIT '' || _relname || '',''; _partition_def = _partition_def || '' DROP CONSTRAINT '' || _const_name; RETURN _partition_def; END; ' LANGUAGE 'plpgsql'; -- -- pgpart.detach_partition() -- -- Detach a partition from the parent table. -- CREATE OR REPLACE FUNCTION pgpart.detach_partition ( NAME, NAME, NAME ) RETURNS BOOLEAN AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname ALIAS FOR $3; _r RECORD; _def TEXT; BEGIN FOR _r IN SELECT pgpart._get_detach_partition_def(_nspname, _relname, _partname) LOOP _def = _r._get_detach_partition_def || '';''; RAISE NOTICE ''detach_partition: %'', _def; EXECUTE _def; END LOOP; RETURN true; END ' LANGUAGE 'plpgsql'; CREATE OR REPLACE FUNCTION pgpart.show_partition ( NAME, NAME ) RETURNS SETOF NAME AS ' DECLARE _nspname ALIAS FOR $1; _relname ALIAS FOR $2; _partname NAME; BEGIN FOR _partname IN SELECT c.relname FROM pg_namespace n, pg_class p, pg_inherits i, pg_class c WHERE n.nspname=_nspname AND n.oid=p.relnamespace AND p.relname=_relname AND p.oid=i.inhparent AND i.inhrelid=c.oid ORDER BY c.relname LOOP RETURN NEXT _partname; END LOOP; END ' LANGUAGE 'plpgsql'; COMMIT;