Copying an Existing Check¶
This is how you customise a built-in check — its definition is locked (see Updating an Existing Check), so if you want your own variant of one, copying it is the only path. It's also useful for any two checks meant to be near-identical, built-in or not: say you like the huge-tables check from the worked example but want a near-identical one for indexes instead — same shape, same thresholding, just a different underlying query. Copying a check clones an existing check's metadata onto a new namespace/category/run-number combination, letting you override just the fields that should actually differ:
-- 1. Write the new check's own function first
CREATE OR REPLACE FUNCTION pghf.chk_custom_huge_indexes(p_run_id uuid)
RETURNS pghf.check_result
LANGUAGE plpgsql
AS $$
DECLARE
v_count int;
v_observed jsonb;
BEGIN
SELECT count(*), jsonb_agg(to_jsonb(t)) INTO v_count, v_observed FROM (
SELECT n.nspname || '.' || c.relname AS index_name,
pg_relation_size(c.oid) AS total_bytes
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'i'
AND pg_relation_size(c.oid) > 50 * 1024^3
ORDER BY total_bytes DESC
) t;
RETURN ROW('COLLECTED',
jsonb_build_object('value', v_count, 'indexes', COALESCE(v_observed, '[]'::jsonb)),
NULL)::pghf.check_result;
END;
$$;
-- 2. Copy X01-001's metadata onto a new check ID, overriding only what differs
SELECT pghf.copy_check(
p_source_check_id => 'X01-001',
p_namespace_code => 'X',
p_category_code => '01',
p_run_number => 2,
p_routine => 'pghf.chk_custom_huge_indexes(uuid)'::regprocedure,
p_check_name => 'Indexes over 50GB',
p_rationale => 'Same rationale as X01-001, but for indexes — an oversized index changes '
'REINDEX CONCURRENTLY sizing and maintenance-window planning the same way.',
p_description => 'Flags any index exceeding 50GB total size.',
p_copy_thresholds => true
);
-- => 'X01-002'
Every parameter besides the source check ID, the destination namespace/category/run-number, and the new backing function defaults to empty, meaning inherit the source check's current value — the result type, unit, boolean pass value, numeric direction, perfect value, and documentation link were all left unspecified above, and came through unchanged from X01-001 (integer/count/lower_is_better/0). This is the opposite convention from updating a check, where every field is required — a copy's whole point is "same as the source, except the couple of things I'm naming," not a full replace. If you need to explicitly clear a field the source had set, follow up with the update function on the new check ID afterwards.
The backing function is the one field that can't be inherited, and so has no default at all — a check's backing function must be unique to it, and the source's function is already backing the source check, so reusing it would always fail. Write the new check's function first, exactly the same as any brand-new check.
Copying a check delegates its actual insert to pghf.create_check(), so it inherits the exact same guards: the destination combination must not already exist, and the built-in PGHF namespace is rejected as a destination the same way it is when creating a check directly — you can copy from a built-in check into your own namespace, just not into the built-in one. The copy is also always registered as a non-built-in check — there's no way to override that — since a copy is a customisation, never a second row claiming to be a framework-shipped built-in.
Passing p_copy_thresholds => true (the default is false) copies the source's check-level default threshold — not any per-suite override, and not suite membership; add those yourself the same way you would for a brand-new check. If the source's default threshold has a custom evaluator attached, the copy carries it over as-is, but reports a warning: most custom evaluators are written to look up one specific check by ID (see Writing a Custom Evaluator), so a copied evaluator will keep evaluating the source check's data until you write and register one that actually targets the new check.
4. Add it to a suite¶
SELECT pghf.add_check_to_run('default', 'X01-001');
-- or, to control position explicitly:
SELECT pghf.add_check_to_run('default', 'X01-001', p_position => 5);
-- or create your own suite entirely:
SELECT pghf.create_check_run('my_suite', 'my own subset');
SELECT pghf.add_checks_to_run('my_suite', 'PGHF01-003', 'PGHF01-005', 'X01-001');
Adding a check to a suite still takes the human-readable check ID string — it resolves that to the check's internal identity for you, so nothing about this call changes even though the underlying reference doesn't.
5. Done¶
The next collection run against that suite picks it up automatically:
SELECT check_id, status, observed FROM pghf.raw_results
WHERE check_id = 'X01-001'
ORDER BY collected_at DESC LIMIT 1;
(Querying the raw-results table directly like this is an operator action — the same role needs its own read permission on it regardless, since you'd already need insert permission there just to call the collection engine in the first place. A read-only consumer with no direct table grants would use the reporting functions instead — see The Access Model in the User Guide.)
Continue to Building a Suite by Pattern.