blob: dbf6ce0014afac015c25035416d2c24775cfabc5 [file]
/* gpcontrib/gp_toolkit/gp_toolkit--1.3--1.4.sql */
-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION gp_toolkit UPDATE TO '1.4'" to load this file. \quit
-- Check orphaned data files on default and user tablespaces.
-- Compared to the previous version, add gp_segment_id to show which segment it is being executed.
CREATE OR REPLACE VIEW gp_toolkit.__check_orphaned_files AS
SELECT f1.tablespace, f1.filename, f1.filepath, pg_catalog.gp_execution_segment() AS gp_segment_id
from gp_toolkit.__get_exist_files f1
LEFT JOIN gp_toolkit.__get_expect_files f2
ON f1.tablespace = f2.tablespace AND substring(f1.filename from '[0-9]+') = f2.filename
WHERE f2.tablespace IS NULL
AND f1.filename SIMILAR TO '[0-9]+(\.)?(\_)?%';
-- Function to check orphaned files.
-- Compared to the previous version, adjust the SELECT ... FROM __check_orphaned_files since we added new column to it.
-- NOTE: this function does the same lock and checks as gp_toolkit.gp_move_orphaned_files(), and it needs to be that way.
CREATE OR REPLACE FUNCTION gp_toolkit.__gp_check_orphaned_files_func()
RETURNS TABLE (
gp_segment_id int,
tablespace oid,
filename text,
filepath text
)
LANGUAGE plpgsql AS $$
BEGIN
BEGIN
-- lock pg_class so that no one will be adding/altering relfilenodes
LOCK TABLE pg_class IN SHARE MODE NOWAIT;
-- make sure no other active/idle transaction is running
IF EXISTS (
SELECT 1
FROM gp_stat_activity
WHERE
sess_id <> -1 AND backend_type IN ('client backend', 'unknown process type') -- exclude background worker types
AND sess_id <> current_setting('gp_session_id')::int -- Exclude the current session
) THEN
RAISE EXCEPTION 'There is a client session running on one or more segment. Aborting...';
END IF;
-- force checkpoint to make sure we do not include files that are normally pending delete
CHECKPOINT;
RETURN QUERY
SELECT v.gp_segment_id, v.tablespace, v.filename, v.filepath
FROM gp_dist_random('gp_toolkit.__check_orphaned_files') v
UNION ALL
SELECT -1 AS gp_segment_id, v.tablespace, v.filename, v.filepath
FROM gp_toolkit.__check_orphaned_files v;
EXCEPTION
WHEN lock_not_available THEN
RAISE EXCEPTION 'cannot obtain SHARE lock on pg_class';
WHEN OTHERS THEN
RAISE;
END;
RETURN;
END;
$$;
-- Function to move orphaned files to a designated location.
-- NOTE: this function does the same lock and checks as gp_toolkit.__gp_check_orphaned_files_func(), and it needs to be that way.
CREATE FUNCTION gp_toolkit.gp_move_orphaned_files(target_location TEXT) RETURNS TABLE (
gp_segment_id INT,
move_success BOOL,
oldpath TEXT,
newpath TEXT
)
LANGUAGE plpgsql AS $$
BEGIN
-- lock pg_class so that no one will be adding/altering relfilenodes
LOCK TABLE pg_class IN SHARE MODE NOWAIT;
-- make sure no other active/idle transaction is running
IF EXISTS (
SELECT 1
FROM gp_stat_activity
WHERE
sess_id <> -1 AND backend_type IN ('client backend', 'unknown process type') -- exclude background worker types
AND sess_id <> current_setting('gp_session_id')::int -- Exclude the current session
) THEN
RAISE EXCEPTION 'There is a client session running on one or more segment. Aborting...';
END IF;
-- force checkpoint to make sure we do not include files that are normally pending delete
CHECKPOINT;
RETURN QUERY
SELECT
q.gp_segment_id,
q.move_success,
q.oldpath,
q.newpath
FROM (
WITH OrphanedFiles AS (
-- Coordinator
SELECT
o.gp_segment_id,
s.setting || '/' || o.filepath as oldpath,
target_location || '/seg' || o.gp_segment_id::text || '_' || REPLACE(o.filepath, '/', '_') as newpath
FROM gp_toolkit.__check_orphaned_files o
JOIN gp_settings s on o.gp_segment_id = s.gp_segment_id
WHERE s.name = 'data_directory'
UNION ALL
-- Segments
SELECT
o.gp_segment_id,
s.setting || '/' || o.filepath as oldpath,
target_location || '/seg' || o.gp_segment_id::text || '_' || REPLACE(o.filepath, '/', '_') as newpath
FROM gp_dist_random('gp_toolkit.__check_orphaned_files') o
JOIN gp_settings s on o.gp_segment_id = s.gp_segment_id
WHERE s.name = 'data_directory'
)
SELECT
OrphanedFiles.gp_segment_id,
OrphanedFiles.oldpath,
OrphanedFiles.newpath,
pg_file_rename(OrphanedFiles.oldpath, OrphanedFiles.newpath, NULL) AS move_success
FROM OrphanedFiles
) q ORDER BY q.gp_segment_id, q.oldpath;
EXCEPTION
WHEN lock_not_available THEN
RAISE EXCEPTION 'cannot obtain SHARE lock on pg_class';
WHEN OTHERS THEN
RAISE;
END;
$$;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_rank
-- @in:
-- oid - oid of table for which to determine range partition rank
-- @out:
-- int - range partition rank
--
-- @doc:
-- Get range partition child rank for given table
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_rank(rp regclass)
RETURNS int
AS 'gp_toolkit.so', 'pg_partition_rank'
LANGUAGE C VOLATILE STRICT NO SQL;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_rank(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_bound_value
-- @in:
-- oid - oid of the partition to get the bound value for
-- text - bound type: 'from', 'to' for a range partition, or 'in' for a list partition
-- @out:
-- text - bound value
--
-- @doc:
-- Get the partition bound value for a given partitioned table
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_bound_value(partrel regclass, bound_type text)
RETURNS text AS $$
DECLARE
v_relpartbound text;
v_bound_value text;
v_parent_table regclass;
v_nkeys int;
BEGIN
-- Check if the given table is a non-default child partition
SELECT inhparent INTO v_parent_table
FROM pg_inherits
WHERE inhrelid = partrel;
IF v_parent_table IS NULL THEN
RETURN NULL;
END IF;
-- Check if the parent table is partitioned by a single key
SELECT partnatts INTO v_nkeys
FROM pg_partitioned_table
WHERE partrelid = v_parent_table;
IF v_nkeys IS NOT NULL AND v_nkeys != 1 THEN
RETURN NULL;
END IF;
-- Get the partition bounds
SELECT pg_get_expr(relpartbound, oid) INTO v_relpartbound
FROM pg_class
WHERE oid = partrel;
-- Parse the bound value from relpartbound
IF lower(bound_type) = 'from' THEN
SELECT (regexp_matches(v_relpartbound, 'FOR VALUES FROM \((.+)\) TO \((.+)\)'))[1] INTO v_bound_value;
ELSIF lower(bound_type) = 'to' THEN
SELECT (regexp_matches(v_relpartbound, 'FOR VALUES FROM \((.+)\) TO \((.+)\)'))[2] INTO v_bound_value;
ELSIF lower(bound_type) = 'in' THEN
SELECT (regexp_matches(v_relpartbound, 'FOR VALUES IN \((.+)\)'))[1] INTO v_bound_value;
ELSE
RAISE EXCEPTION 'Invalid bound type: %', bound_type;
END IF;
RETURN v_bound_value;
END;
$$ LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_bound_value(regclass,text) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_range_from
-- @in:
-- oid - oid of the range partition to get the lower bound value for
-- @out:
-- text - bound value
--
-- @doc:
-- Get the lower bound value for a given range partition child.
-- wrapper around pg_partition_bound_value()
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_range_from(rp regclass)
RETURNS text AS $$
SELECT gp_toolkit.pg_partition_bound_value(rp, 'from');
$$ LANGUAGE sql;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_range_from(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_range_to
-- @in:
-- oid - oid of the range partition to get the upper bound value for
-- @out:
-- text - bound value
--
-- @doc:
-- Get the upper bound value for a given range partition child.
-- wrapper around pg_partition_bound_value()
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_range_to(rp regclass)
RETURNS text AS $$
SELECT gp_toolkit.pg_partition_bound_value(rp, 'to');
$$ LANGUAGE sql;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_range_to(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_list_values
-- @in:
-- oid - oid of the list partition to get the bound value for
-- @out:
-- text - bound value
--
-- @doc:
-- Get the list of values for a given list partition child.
-- wrapper around pg_partition_bound_value()
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_list_values(rp regclass)
RETURNS text AS $$
SELECT gp_toolkit.pg_partition_bound_value(rp, 'in'::text);
$$ LANGUAGE sql;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_list_values(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_isdefault
-- @in:
-- oid - oid of the partition to check
-- @out:
-- boolean - true if the partition is the default partition
--
-- @doc:
-- Get whether a given partition is a default partition
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_isdefault(relid regclass)
RETURNS BOOLEAN AS $$
DECLARE
boundspec TEXT;
BEGIN
-- Get the partition bound definition for the relation
SELECT pg_get_expr(relpartbound, oid) INTO boundspec
FROM pg_catalog.pg_class
WHERE oid = relid;
-- If partition_def is null, the relation is not a partition at all
IF boundspec IS NULL THEN
RETURN FALSE;
END IF;
-- Check if the partition bound spec exactly matches 'DEFAULT'
RETURN boundspec = 'DEFAULT';
END;
$$ LANGUAGE plpgsql;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_isdefault(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_lowest_child
-- @in:
-- oid - oid of a range partitioned table
-- @out:
-- oid - oid of the lowest child partition
--
-- @doc:
-- Get the oid of the lowest child partition for a given partitioned table
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_lowest_child(rp regclass)
RETURNS regclass
AS 'gp_toolkit.so', 'pg_partition_lowest_child'
LANGUAGE C VOLATILE STRICT NO SQL;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_lowest_child(regclass) TO public;
--------------------------------------------------------------------------------
-- @function:
-- gp_toolkit.pg_partition_highest_child
-- @in:
-- oid - oid of a range partitioned table
-- @out:
-- oid - oid of the highest child partition
--
-- @doc:
-- Get the oid of the lowest child partition for a given partitioned table
--
--------------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION gp_toolkit.pg_partition_highest_child(rp regclass)
RETURNS regclass
AS 'gp_toolkit.so', 'pg_partition_highest_child'
LANGUAGE C VOLATILE STRICT NO SQL;
GRANT EXECUTE ON FUNCTION gp_toolkit.pg_partition_highest_child(regclass) TO public;
--------------------------------------------------------------------------------
-- Supporting cast for gp_toolkit.gp_partitions
--------------------------------------------------------------------------------
CREATE TYPE get_partition_result AS (
relid regclass,
parentid regclass,
isleaf bool,
partitionlevel int,
partitiontype text,
partitionrank int,
is_default bool
);
CREATE OR REPLACE FUNCTION gp_toolkit.gp_get_partitions(rp regclass)
RETURNS SETOF get_partition_result
AS 'gp_toolkit.so', 'gp_get_partitions'
LANGUAGE C VOLATILE STRICT NO SQL;
GRANT EXECUTE ON FUNCTION gp_toolkit.gp_get_partitions(regclass) TO public;
--------------------------------------------------------------------------------
-- @view:
-- gp_toolkit.gp_partitions
--
-- @doc:
-- This view provides necessary information about all the child
-- partitions. It plays similar role as the pg_partitions view in 6X and
-- has most of the same columns.
--
-- Notable differences from 6X:
-- (1) The root is level = 0, its immediate children are level 1 and so on.
-- (2) Immediate children of the root have parentpartitiontablename equal
-- to that of the root (instead of being null).
--------------------------------------------------------------------------------
CREATE OR REPLACE VIEW gp_toolkit.gp_partitions AS
WITH default_ts(default_spcname) AS
(select s.spcname
from pg_database, pg_tablespace s
where datname = current_database() and dattablespace = s.oid),
partitions AS
(SELECT p.*,
pg_get_expr(pc.relpartbound, pc.oid) AS bound,
rns.nspname AS rootnamespacename,
pns.nspname AS partitionschemaname,
pc.relname AS partitiontablename,
coalesce(rt.spcname, default_spcname) AS parenttablespacename,
coalesce(pt.spcname, default_spcname) AS partitiontablespacename
FROM
(SELECT relnamespace,
relname AS roottablename,
(gp_toolkit.gp_get_partitions(oid)).*
FROM pg_class
WHERE relkind = 'p'
AND oid NOT IN (SELECT inhrelid FROM pg_inherits)) p
JOIN pg_class pc ON p.relid = pc.oid
JOIN pg_class parentc ON parentc.oid = p.parentid
LEFT JOIN pg_namespace rns ON p.relnamespace = rns.oid
LEFT JOIN pg_namespace pns ON pc.relnamespace = pns.oid
LEFT JOIN pg_tablespace rt ON parentc.reltablespace = rt.oid
LEFT JOIN pg_tablespace pt ON pc.reltablespace = pt.oid
JOIN default_ts ON 1=1)
SELECT
rootnamespacename AS schemaname,
roottablename AS tablename,
partitionschemaname,
partitiontablename,
parentid::regclass AS parentpartitiontablename,
partitiontype,
partitionlevel,
partitionrank,
CASE
WHEN partitiontype = 'list'
THEN substring(bound FROM 'FOR VALUES IN \((.+)\)')
END AS partitionlistvalues,
CASE
WHEN partitiontype = 'range'
THEN substring(bound FROM 'FOR VALUES FROM \((.+)\) TO \((.+)\)')
END AS partitionrangestart,
CASE
WHEN partitiontype = 'range'
THEN substring(bound FROM 'TO \((.+)\)')
END AS partitionrangeend,
is_default AS partitionisdefault,
bound AS partitionboundary,
parenttablespacename AS parenttablespace,
partitiontablespacename AS partitiontablespace
FROM partitions;
GRANT SELECT ON gp_toolkit.gp_partitions TO public;