960 lines
44 KiB
PL/PgSQL
960 lines
44 KiB
PL/PgSQL
/* ================================================================
|
|
* c77_dep extension version 1.0.0
|
|
* PostgreSQL Database Dependency Mapping Tool
|
|
*
|
|
* This extension provides tools for analyzing and visualizing
|
|
* dependencies between database objects.
|
|
* ================================================================
|
|
*/
|
|
|
|
-- Complain if script is sourced in psql, rather than via CREATE EXTENSION
|
|
\echo Use "CREATE EXTENSION c77_dep" to load this file. \quit
|
|
|
|
-- Set up the schema
|
|
-- Uncomment if you want to use a dedicated schema instead of public
|
|
-- CREATE SCHEMA IF NOT EXISTS c77_dep;
|
|
|
|
-- Core dependency mapping view (base version)
|
|
CREATE OR REPLACE VIEW public.c77_dep_dependencies_map
|
|
AS
|
|
WITH RECURSIVE ctedepends AS (
|
|
SELECT DISTINCT 1 AS depth,
|
|
source_ns.nspname AS source_schema,
|
|
source_table.relname AS source_rel,
|
|
source_ns.nspname AS inter_schema,
|
|
source_table.relname AS inter_rel,
|
|
dependent_ns.nspname AS dependent_schema,
|
|
dependent_view.relname AS dependent_rel
|
|
FROM pg_depend
|
|
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
|
JOIN pg_class dependent_view ON dependent_view.oid = pg_rewrite.ev_class
|
|
JOIN pg_class source_table ON source_table.oid = pg_depend.refobjid
|
|
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
|
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
|
WHERE NOT (dependent_ns.nspname = source_ns.nspname AND dependent_view.relname = source_table.relname)
|
|
UNION
|
|
SELECT DISTINCT cd.depth + 1 AS depth,
|
|
cd.source_schema,
|
|
cd.source_rel,
|
|
source_ns.nspname AS inter_schema,
|
|
source_table.relname AS inter_rel,
|
|
dependent_ns.nspname AS dependent_schema,
|
|
dependent_view.relname AS dependent_rel
|
|
FROM pg_depend
|
|
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
|
JOIN pg_class dependent_view ON pg_rewrite.ev_class = dependent_view.oid
|
|
JOIN pg_class source_table ON pg_depend.refobjid = source_table.oid
|
|
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
|
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
|
JOIN ctedepends cd ON cd.dependent_schema = source_ns.nspname AND cd.dependent_rel = source_table.relname AND NOT (dependent_ns.nspname = cd.dependent_schema AND dependent_view.relname = cd.dependent_rel)
|
|
), ctedependents AS (
|
|
SELECT x.source,
|
|
string_agg(x.dependent, ' | '::text) AS dependents,
|
|
count(*) AS deps
|
|
FROM ( SELECT DISTINCT (ctedepends.source_schema::text || '.'::text) || ctedepends.source_rel::text AS source,
|
|
(ctedepends.dependent_schema::text || '.'::text) || ctedepends.dependent_rel::text AS dependent
|
|
FROM ctedepends
|
|
WHERE 1 = ctedepends.depth) x
|
|
GROUP BY x.source
|
|
ORDER BY x.source
|
|
), cteadddependents AS (
|
|
SELECT x.source,
|
|
string_agg(DISTINCT x.dependent, ' | '::text) AS dependents,
|
|
count(*) AS deps
|
|
FROM ( SELECT DISTINCT (dep_1.source_schema::text || '.'::text) || dep_1.source_rel::text AS source,
|
|
(dep_1.dependent_schema::text || '.'::text) || dep_1.dependent_rel::text AS dependent
|
|
FROM ctedepends dep_1
|
|
JOIN ctedependents deps ON deps.source = ((dep_1.source_schema::text || '.'::text) || dep_1.source_rel::text)
|
|
WHERE 1 < dep_1.depth AND 0 = POSITION((((dep_1.dependent_schema::text || '.'::text) || dep_1.dependent_rel::text) || ' |'::text) IN (deps.dependents || ' |'::text))) x
|
|
GROUP BY x.source
|
|
ORDER BY x.source
|
|
), cterequires AS (
|
|
SELECT DISTINCT ns_r.nspname AS basensp,
|
|
cl_r.relname AS basename,
|
|
ns_r.nspname AS relnsp,
|
|
cl_r.relname,
|
|
ns_d.nspname AS reqnsp,
|
|
cl_d.relname AS reqname,
|
|
1 AS depth
|
|
FROM pg_rewrite r
|
|
JOIN pg_class cl_r ON cl_r.oid = r.ev_class
|
|
JOIN pg_namespace ns_r ON ns_r.oid = cl_r.relnamespace
|
|
JOIN pg_depend d ON d.objid = r.oid
|
|
JOIN pg_class cl_d ON cl_d.oid = d.refobjid
|
|
JOIN pg_namespace ns_d ON ns_d.oid = cl_d.relnamespace
|
|
WHERE (cl_d.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'v'::"char"])) AND (ns_r.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name])) AND ns_r.nspname !~~ 'pg_toast%'::text AND ((ns_r.nspname::text || '.'::text) || cl_r.relname::text) <> ((ns_d.nspname::text || '.'::text) || cl_d.relname::text)
|
|
UNION
|
|
SELECT cterequires.basensp,
|
|
cterequires.basename,
|
|
ns_r.nspname AS relnsp,
|
|
cl_r.relname,
|
|
ns_d.nspname AS reqnsp,
|
|
cl_d.relname AS reqname,
|
|
cterequires.depth + 1 AS depth
|
|
FROM pg_rewrite r
|
|
JOIN pg_class cl_r ON cl_r.oid = r.ev_class
|
|
JOIN pg_namespace ns_r ON ns_r.oid = cl_r.relnamespace
|
|
JOIN pg_depend d ON d.objid = r.oid
|
|
JOIN pg_class cl_d ON cl_d.oid = d.refobjid
|
|
JOIN pg_namespace ns_d ON ns_d.oid = cl_d.relnamespace
|
|
JOIN cterequires ON cterequires.reqnsp = ns_r.nspname AND cterequires.reqname = cl_r.relname AND NOT (cterequires.reqnsp = ns_d.nspname AND cterequires.reqname = cl_d.relname)
|
|
WHERE ((ns_r.nspname::text || '.'::text) || cl_r.relname::text) <> ((ns_d.nspname::text || '.'::text) || cl_d.relname::text) AND (cl_d.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'v'::"char"])) AND ns_r.nspname !~~ 'pg_toast%'::text AND (ns_r.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name]))
|
|
), cterequirements AS (
|
|
SELECT DISTINCT (cterequires.basensp::text || '.'::text) || cterequires.basename::text AS rel,
|
|
count(*) AS reqs,
|
|
string_agg((cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text, ' | '::text) AS reqlist
|
|
FROM cterequires
|
|
WHERE 1 = cterequires.depth
|
|
GROUP BY ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
), cteaddrequirements AS (
|
|
SELECT DISTINCT (cterequires.basensp::text || '.'::text) || cterequires.basename::text AS rel,
|
|
count(*) AS addreqs,
|
|
string_agg(DISTINCT (cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text, ' | '::text) AS addreqlist
|
|
FROM cterequires
|
|
JOIN cterequirements ON cterequirements.rel = ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
WHERE 1 < cterequires.depth AND 0 = POSITION((((cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text) || ' |'::text) IN (cterequirements.reqlist || ' |'::text))
|
|
GROUP BY ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
)
|
|
SELECT (nsp.nspname::text || '.'::text) || cls.relname::text AS relation,
|
|
CASE cls.relkind
|
|
WHEN 'r'::"char" THEN 'TABLE'::text
|
|
WHEN 'v'::"char" THEN 'VIEW'::text
|
|
WHEN 'm'::"char" THEN 'MATV'::text
|
|
WHEN 'i'::"char" THEN 'INDEX'::text
|
|
WHEN 'S'::"char" THEN 'SEQUENCE'::text
|
|
WHEN 'c'::"char" THEN 'TYPE'::text
|
|
ELSE cls.relkind::text
|
|
END AS object_type,
|
|
rol.rolname AS owner,
|
|
COALESCE(dep.deps, 0::bigint) AS deps,
|
|
CASE
|
|
WHEN ''::text <> depadd.dependents THEN 1 + (length(depadd.dependents) - length(replace(depadd.dependents, '|'::text, ''::text)))
|
|
ELSE 0
|
|
END AS add_deps,
|
|
COALESCE(req.reqs, 0::bigint) AS reqs,
|
|
CASE
|
|
WHEN ''::text <> addreq.addreqlist THEN 1 + (length(addreq.addreqlist) - length(replace(addreq.addreqlist, '|'::text, ''::text)))
|
|
ELSE 0
|
|
END AS add_reqs,
|
|
COALESCE(dep.dependents, ''::text) AS dependents,
|
|
COALESCE(depadd.dependents, ''::text) AS add_dependents,
|
|
COALESCE(req.reqlist, ''::text) AS requirements,
|
|
COALESCE(addreq.addreqlist, ''::text) AS add_requirements
|
|
FROM pg_class cls
|
|
JOIN pg_namespace nsp ON nsp.oid = cls.relnamespace
|
|
JOIN pg_roles rol ON rol.oid = cls.relowner
|
|
LEFT JOIN ctedependents dep ON dep.source = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cteadddependents depadd ON depadd.source = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cterequirements req ON req.rel = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cteaddrequirements addreq ON addreq.rel = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
WHERE (cls.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'm'::"char"])) AND nsp.nspname !~~ 'pg_toast%'::text AND (nsp.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name]))
|
|
ORDER BY nsp.nspname, cls.relname;
|
|
|
|
COMMENT ON VIEW public.c77_dep_dependencies_map IS 'Maps dependencies between database objects (tables, views, materialized views)';
|
|
|
|
-- Enhanced dependency map with foreign tables
|
|
CREATE OR REPLACE VIEW public.c77_dep_dependencies_map_with_foreign
|
|
AS
|
|
WITH RECURSIVE ctedepends AS (
|
|
SELECT DISTINCT 1 AS depth,
|
|
source_ns.nspname AS source_schema,
|
|
source_table.relname AS source_rel,
|
|
source_ns.nspname AS inter_schema,
|
|
source_table.relname AS inter_rel,
|
|
dependent_ns.nspname AS dependent_schema,
|
|
dependent_view.relname AS dependent_rel
|
|
FROM pg_depend
|
|
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
|
JOIN pg_class dependent_view ON dependent_view.oid = pg_rewrite.ev_class
|
|
JOIN pg_class source_table ON source_table.oid = pg_depend.refobjid
|
|
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
|
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
|
WHERE NOT (dependent_ns.nspname = source_ns.nspname AND dependent_view.relname = source_table.relname)
|
|
UNION
|
|
SELECT DISTINCT cd.depth + 1 AS depth,
|
|
cd.source_schema,
|
|
cd.source_rel,
|
|
source_ns.nspname AS inter_schema,
|
|
source_table.relname AS inter_rel,
|
|
dependent_ns.nspname AS dependent_schema,
|
|
dependent_view.relname AS dependent_rel
|
|
FROM pg_depend
|
|
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
|
|
JOIN pg_class dependent_view ON pg_rewrite.ev_class = dependent_view.oid
|
|
JOIN pg_class source_table ON pg_depend.refobjid = source_table.oid
|
|
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
|
|
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
|
|
JOIN ctedepends cd ON cd.dependent_schema = source_ns.nspname AND cd.dependent_rel = source_table.relname AND NOT (dependent_ns.nspname = cd.dependent_schema AND dependent_view.relname = cd.dependent_rel)
|
|
), ctedependents AS (
|
|
SELECT x.source,
|
|
string_agg(x.dependent, ' | '::text) AS dependents,
|
|
count(*) AS deps
|
|
FROM ( SELECT DISTINCT (ctedepends.source_schema::text || '.'::text) || ctedepends.source_rel::text AS source,
|
|
(ctedepends.dependent_schema::text || '.'::text) || ctedepends.dependent_rel::text AS dependent
|
|
FROM ctedepends
|
|
WHERE 1 = ctedepends.depth) x
|
|
GROUP BY x.source
|
|
ORDER BY x.source
|
|
), cteadddependents AS (
|
|
SELECT x.source,
|
|
string_agg(DISTINCT x.dependent, ' | '::text) AS dependents,
|
|
count(*) AS deps
|
|
FROM ( SELECT DISTINCT (dep_1.source_schema::text || '.'::text) || dep_1.source_rel::text AS source,
|
|
(dep_1.dependent_schema::text || '.'::text) || dep_1.dependent_rel::text AS dependent
|
|
FROM ctedepends dep_1
|
|
JOIN ctedependents deps ON deps.source = ((dep_1.source_schema::text || '.'::text) || dep_1.source_rel::text)
|
|
WHERE 1 < dep_1.depth AND 0 = POSITION((((dep_1.dependent_schema::text || '.'::text) || dep_1.dependent_rel::text) || ' |'::text) IN (deps.dependents || ' |'::text))) x
|
|
GROUP BY x.source
|
|
ORDER BY x.source
|
|
), cterequires AS (
|
|
SELECT DISTINCT ns_r.nspname AS basensp,
|
|
cl_r.relname AS basename,
|
|
ns_r.nspname AS relnsp,
|
|
cl_r.relname,
|
|
ns_d.nspname AS reqnsp,
|
|
cl_d.relname AS reqname,
|
|
1 AS depth
|
|
FROM pg_rewrite r
|
|
JOIN pg_class cl_r ON cl_r.oid = r.ev_class
|
|
JOIN pg_namespace ns_r ON ns_r.oid = cl_r.relnamespace
|
|
JOIN pg_depend d ON d.objid = r.oid
|
|
JOIN pg_class cl_d ON cl_d.oid = d.refobjid
|
|
JOIN pg_namespace ns_d ON ns_d.oid = cl_d.relnamespace
|
|
WHERE (cl_d.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'v'::"char", 'f'::"char"])) AND (ns_r.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name])) AND ns_r.nspname !~~ 'pg_toast%'::text AND ((ns_r.nspname::text || '.'::text) || cl_r.relname::text) <> ((ns_d.nspname::text || '.'::text) || cl_d.relname::text)
|
|
UNION
|
|
SELECT cterequires.basensp,
|
|
cterequires.basename,
|
|
ns_r.nspname AS relnsp,
|
|
cl_r.relname,
|
|
ns_d.nspname AS reqnsp,
|
|
cl_d.relname AS reqname,
|
|
cterequires.depth + 1 AS depth
|
|
FROM pg_rewrite r
|
|
JOIN pg_class cl_r ON cl_r.oid = r.ev_class
|
|
JOIN pg_namespace ns_r ON ns_r.oid = cl_r.relnamespace
|
|
JOIN pg_depend d ON d.objid = r.oid
|
|
JOIN pg_class cl_d ON cl_d.oid = d.refobjid
|
|
JOIN pg_namespace ns_d ON ns_d.oid = cl_d.relnamespace
|
|
JOIN cterequires ON cterequires.reqnsp = ns_r.nspname AND cterequires.reqname = cl_r.relname AND NOT (cterequires.reqnsp = ns_d.nspname AND cterequires.reqname = cl_d.relname)
|
|
WHERE ((ns_r.nspname::text || '.'::text) || cl_r.relname::text) <> ((ns_d.nspname::text || '.'::text) || cl_d.relname::text) AND (cl_d.relkind = ANY (ARRAY['r'::"char", 'm'::"char", 'v'::"char", 'f'::"char"])) AND ns_r.nspname !~~ 'pg_toast%'::text AND (ns_r.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name]))
|
|
), cterequirements AS (
|
|
SELECT DISTINCT (cterequires.basensp::text || '.'::text) || cterequires.basename::text AS rel,
|
|
count(*) AS reqs,
|
|
string_agg((cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text, ' | '::text) AS reqlist
|
|
FROM cterequires
|
|
WHERE 1 = cterequires.depth
|
|
GROUP BY ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
), cteaddrequirements AS (
|
|
SELECT DISTINCT (cterequires.basensp::text || '.'::text) || cterequires.basename::text AS rel,
|
|
count(*) AS addreqs,
|
|
string_agg(DISTINCT (cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text, ' | '::text) AS addreqlist
|
|
FROM cterequires
|
|
JOIN cterequirements ON cterequirements.rel = ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
WHERE 1 < cterequires.depth AND 0 = POSITION((((cterequires.reqnsp::text || '.'::text) || cterequires.reqname::text) || ' |'::text) IN (cterequirements.reqlist || ' |'::text))
|
|
GROUP BY ((cterequires.basensp::text || '.'::text) || cterequires.basename::text)
|
|
)
|
|
SELECT (nsp.nspname::text || '.'::text) || cls.relname::text AS relation,
|
|
CASE cls.relkind
|
|
WHEN 'r'::"char" THEN 'TABLE'::text
|
|
WHEN 'v'::"char" THEN 'VIEW'::text
|
|
WHEN 'm'::"char" THEN 'MATV'::text
|
|
WHEN 'f'::"char" THEN 'FOREIGN TABLE'::text
|
|
WHEN 'i'::"char" THEN 'INDEX'::text
|
|
WHEN 'S'::"char" THEN 'SEQUENCE'::text
|
|
WHEN 'c'::"char" THEN 'TYPE'::text
|
|
ELSE cls.relkind::text
|
|
END AS object_type,
|
|
rol.rolname AS owner,
|
|
COALESCE(dep.deps, 0::bigint) AS deps,
|
|
CASE
|
|
WHEN ''::text <> depadd.dependents THEN 1 + (length(depadd.dependents) - length(replace(depadd.dependents, '|'::text, ''::text)))
|
|
ELSE 0
|
|
END AS add_deps,
|
|
COALESCE(req.reqs, 0::bigint) AS reqs,
|
|
CASE
|
|
WHEN ''::text <> addreq.addreqlist THEN 1 + (length(addreq.addreqlist) - length(replace(addreq.addreqlist, '|'::text, ''::text)))
|
|
ELSE 0
|
|
END AS add_reqs,
|
|
COALESCE(dep.dependents, ''::text) AS dependents,
|
|
COALESCE(depadd.dependents, ''::text) AS add_dependents,
|
|
COALESCE(req.reqlist, ''::text) AS requirements,
|
|
COALESCE(addreq.addreqlist, ''::text) AS add_requirements
|
|
FROM pg_class cls
|
|
JOIN pg_namespace nsp ON nsp.oid = cls.relnamespace
|
|
JOIN pg_roles rol ON rol.oid = cls.relowner
|
|
LEFT JOIN ctedependents dep ON dep.source = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cteadddependents depadd ON depadd.source = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cterequirements req ON req.rel = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
LEFT JOIN cteaddrequirements addreq ON addreq.rel = ((nsp.nspname::text || '.'::text) || cls.relname::text)
|
|
WHERE (cls.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'm'::"char", 'f'::"char"])) AND nsp.nspname !~~ 'pg_toast%'::text AND (nsp.nspname <> ALL (ARRAY['information_schema'::name, 'pg_catalog'::name]))
|
|
ORDER BY nsp.nspname, cls.relname;
|
|
|
|
COMMENT ON VIEW public.c77_dep_dependencies_map_with_foreign IS 'Maps dependencies between database objects including foreign tables';
|
|
|
|
-- Circular Dependency Detector
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_detect_circular_dependencies(
|
|
)
|
|
RETURNS TABLE(object1 text, object2 text, dependency_type text)
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
ROWS 1000
|
|
|
|
AS $BODY$
|
|
BEGIN
|
|
RETURN QUERY
|
|
-- Direct circular dependencies
|
|
SELECT
|
|
a.relation AS object1,
|
|
b.relation AS object2,
|
|
'Direct circular' AS dependency_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign a
|
|
JOIN public.c77_dep_dependencies_map_with_foreign b ON
|
|
a.relation <> b.relation AND
|
|
a.requirements LIKE '%' || b.relation || '%' AND
|
|
b.requirements LIKE '%' || a.relation || '%'
|
|
|
|
UNION
|
|
|
|
-- Indirect circular dependencies
|
|
SELECT
|
|
a.relation AS object1,
|
|
b.relation AS object2,
|
|
'Indirect circular' AS dependency_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign a
|
|
JOIN public.c77_dep_dependencies_map_with_foreign b ON
|
|
a.relation <> b.relation AND
|
|
a.add_requirements LIKE '%' || b.relation || '%' AND
|
|
b.add_requirements LIKE '%' || a.relation || '%'
|
|
WHERE NOT EXISTS (
|
|
SELECT 1
|
|
FROM public.c77_dep_dependencies_map_with_foreign c
|
|
JOIN public.c77_dep_dependencies_map_with_foreign d ON
|
|
c.relation = a.relation AND
|
|
d.relation = b.relation AND
|
|
c.requirements LIKE '%' || d.relation || '%' AND
|
|
d.requirements LIKE '%' || c.relation || '%'
|
|
);
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_detect_circular_dependencies() IS 'Detects circular dependencies between database objects';
|
|
|
|
-- Orphaned Objects Tracker
|
|
CREATE OR REPLACE VIEW public.c77_dep_orphaned_objects
|
|
AS
|
|
SELECT relation,
|
|
object_type,
|
|
owner
|
|
FROM c77_dep_dependencies_map_with_foreign
|
|
WHERE deps = 0 AND reqs = 0;
|
|
|
|
COMMENT ON VIEW public.c77_dep_orphaned_objects IS 'Identifies database objects with no dependencies or requirements';
|
|
|
|
-- Schema Complexity Assessment
|
|
CREATE OR REPLACE VIEW public.c77_dep_schema_complexity
|
|
AS
|
|
SELECT split_part(relation, '.'::text, 1) AS schema_name,
|
|
count(*) AS total_objects,
|
|
sum(deps) AS total_dependencies,
|
|
round(avg(deps), 2) AS avg_dependencies_per_object,
|
|
max(deps) AS max_dependencies,
|
|
count(*) FILTER (WHERE deps > 10) AS highly_dependent_objects
|
|
FROM c77_dep_dependencies_map_with_foreign
|
|
GROUP BY (split_part(relation, '.'::text, 1))
|
|
ORDER BY (sum(deps)) DESC;
|
|
|
|
COMMENT ON VIEW public.c77_dep_schema_complexity IS 'Provides metrics on schema complexity and dependencies';
|
|
|
|
-- Database Object Type Summary
|
|
CREATE OR REPLACE VIEW public.c77_dep_object_type_summary
|
|
AS
|
|
SELECT object_type,
|
|
count(*) AS object_count,
|
|
round(100.0 * count(*)::numeric / sum(count(*)) OVER (), 2) AS percentage,
|
|
sum(deps) AS total_dependencies,
|
|
round(avg(deps), 2) AS avg_dependencies_per_object,
|
|
max(deps) AS max_dependencies
|
|
FROM c77_dep_dependencies_map_with_foreign
|
|
GROUP BY object_type
|
|
ORDER BY (count(*)) DESC;
|
|
|
|
COMMENT ON VIEW public.c77_dep_object_type_summary IS 'Provides a summary of object types and their dependency characteristics';
|
|
|
|
-- Dependency Risk Assessment
|
|
CREATE OR REPLACE VIEW public.c77_dep_risk_assessment
|
|
AS
|
|
SELECT relation,
|
|
object_type,
|
|
deps,
|
|
add_deps,
|
|
deps * 2 + add_deps +
|
|
CASE
|
|
WHEN object_type = 'VIEW'::text THEN 3
|
|
WHEN object_type = 'MATV'::text THEN 4
|
|
WHEN object_type = 'FOREIGN TABLE'::text THEN 5
|
|
ELSE 1
|
|
END AS risk_score,
|
|
CASE
|
|
WHEN (deps + add_deps) > 20 THEN 'HIGH'::text
|
|
WHEN (deps + add_deps) > 10 THEN 'MEDIUM'::text
|
|
ELSE 'LOW'::text
|
|
END AS risk_level
|
|
FROM c77_dep_dependencies_map_with_foreign;
|
|
|
|
COMMENT ON VIEW public.c77_dep_risk_assessment IS 'Assesses risk level of modifying database objects based on dependencies';
|
|
|
|
-- Dependency Hub Objects
|
|
CREATE OR REPLACE VIEW public.c77_dep_hub_objects
|
|
AS
|
|
SELECT relation,
|
|
object_type,
|
|
deps + add_deps AS total_dependencies,
|
|
deps AS direct_dependencies,
|
|
add_deps AS indirect_dependencies,
|
|
reqs + add_reqs AS total_requirements,
|
|
reqs AS direct_requirements,
|
|
add_reqs AS indirect_requirements
|
|
FROM c77_dep_dependencies_map_with_foreign
|
|
WHERE (deps + add_deps) > 5
|
|
ORDER BY (deps + add_deps) DESC
|
|
LIMIT 20;
|
|
|
|
COMMENT ON VIEW public.c77_dep_hub_objects IS 'Identifies central hub objects with many dependencies';
|
|
|
|
-- Cleanup Candidates
|
|
CREATE OR REPLACE VIEW public.c77_dep_cleanup_candidates
|
|
AS
|
|
SELECT c77_dep_dependencies_map_with_foreign.relation,
|
|
c77_dep_dependencies_map_with_foreign.object_type,
|
|
'No dependencies, potentially unused'::text AS reason
|
|
FROM c77_dep_dependencies_map_with_foreign
|
|
WHERE c77_dep_dependencies_map_with_foreign.deps = 0
|
|
UNION ALL
|
|
SELECT DISTINCT a.relation,
|
|
a.object_type,
|
|
'Circular dependency with '::text || b.relation AS reason
|
|
FROM c77_dep_dependencies_map_with_foreign a
|
|
JOIN c77_dep_dependencies_map_with_foreign b ON a.relation <> b.relation AND (a.requirements ~~ (('%'::text || b.relation) || '%'::text) OR a.add_requirements ~~ (('%'::text || b.relation) || '%'::text)) AND (b.requirements ~~ (('%'::text || a.relation) || '%'::text) OR b.add_requirements ~~ (('%'::text || a.relation) || '%'::text));
|
|
|
|
COMMENT ON VIEW public.c77_dep_cleanup_candidates IS 'Identifies objects that may be candidates for cleanup or refactoring';
|
|
|
|
-- Impact Analysis Function
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_analyze_drop_impact(
|
|
p_object_name text)
|
|
RETURNS TABLE(affected_object text, obj_type text, impact_level integer, dependency_path text)
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
ROWS 1000
|
|
|
|
AS $BODY$
|
|
DECLARE
|
|
affected_objects text[] := '{}';
|
|
current_level int := 0;
|
|
current_batch text[] := ARRAY[p_object_name];
|
|
next_batch text[] := '{}';
|
|
v_rel text;
|
|
v_type text;
|
|
dep text;
|
|
BEGIN
|
|
-- First, check if the object exists
|
|
IF NOT EXISTS (
|
|
SELECT 1 FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE relation = p_object_name
|
|
) THEN
|
|
RAISE EXCEPTION 'Object % not found in dependency map', p_object_name;
|
|
END IF;
|
|
|
|
-- Return the object itself at level 0
|
|
SELECT relation, object_type
|
|
INTO v_rel, v_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE relation = p_object_name;
|
|
|
|
affected_object := v_rel;
|
|
obj_type := v_type;
|
|
impact_level := 0;
|
|
dependency_path := v_rel;
|
|
RETURN NEXT;
|
|
|
|
-- Add to affected objects
|
|
affected_objects := array_append(affected_objects, p_object_name);
|
|
|
|
-- Process levels until no new objects are found
|
|
WHILE array_length(current_batch, 1) > 0 LOOP
|
|
current_level := current_level + 1;
|
|
next_batch := '{}';
|
|
|
|
-- For each object in current batch
|
|
FOREACH dep IN ARRAY current_batch LOOP
|
|
-- Find objects that depend on this one
|
|
FOR v_rel, v_type IN
|
|
SELECT
|
|
dm.relation,
|
|
dm.object_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign dm
|
|
WHERE
|
|
(dm.requirements LIKE '%' || dep || '%' OR
|
|
dm.add_requirements LIKE '%' || dep || '%')
|
|
AND NOT dm.relation = ANY(affected_objects)
|
|
LOOP
|
|
-- Add to results
|
|
affected_object := v_rel;
|
|
obj_type := v_type;
|
|
impact_level := current_level;
|
|
dependency_path := p_object_name || ' -> ' || v_rel;
|
|
RETURN NEXT;
|
|
|
|
-- Add to affected objects and next batch
|
|
affected_objects := array_append(affected_objects, v_rel);
|
|
next_batch := array_append(next_batch, v_rel);
|
|
END LOOP;
|
|
END LOOP;
|
|
|
|
current_batch := next_batch;
|
|
END LOOP;
|
|
|
|
RETURN;
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_analyze_drop_impact(text) IS 'Analyzes impact of dropping a database object by showing affected dependencies';
|
|
|
|
-- Export dependency graph in DOT format
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_export_dependency_graph(
|
|
schema_filter text DEFAULT NULL::text,
|
|
max_depth integer DEFAULT 3,
|
|
include_foreign boolean DEFAULT true)
|
|
RETURNS text
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
AS $BODY$
|
|
DECLARE
|
|
result text := 'digraph dependencies {' || E'\n';
|
|
result_nodes text := '';
|
|
result_edges text := '';
|
|
v_rel record;
|
|
req_array text[];
|
|
req text;
|
|
BEGIN
|
|
-- Create nodes with styling based on object type
|
|
FOR v_rel IN
|
|
SELECT
|
|
relation,
|
|
object_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE (schema_filter IS NULL OR split_part(relation, '.', 1) = schema_filter)
|
|
AND (include_foreign OR object_type <> 'FOREIGN TABLE')
|
|
LOOP
|
|
-- Add node styling based on object type
|
|
result_nodes := result_nodes || ' "' || v_rel.relation || '" [';
|
|
|
|
CASE v_rel.object_type
|
|
WHEN 'TABLE' THEN
|
|
result_nodes := result_nodes || 'shape=box, style=filled, fillcolor=lightblue';
|
|
WHEN 'VIEW' THEN
|
|
result_nodes := result_nodes || 'shape=ellipse, style=filled, fillcolor=lightgreen';
|
|
WHEN 'MATV' THEN
|
|
result_nodes := result_nodes || 'shape=ellipse, style=filled, fillcolor=lightcyan';
|
|
WHEN 'FOREIGN TABLE' THEN
|
|
result_nodes := result_nodes || 'shape=box, style=filled, fillcolor=lightyellow';
|
|
ELSE
|
|
result_nodes := result_nodes || 'shape=diamond';
|
|
END CASE;
|
|
|
|
result_nodes := result_nodes || '];' || E'\n';
|
|
END LOOP;
|
|
|
|
-- Create edges for dependencies
|
|
FOR v_rel IN
|
|
SELECT
|
|
relation,
|
|
requirements
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE (schema_filter IS NULL OR split_part(relation, '.', 1) = schema_filter)
|
|
AND (include_foreign OR object_type <> 'FOREIGN TABLE')
|
|
AND requirements IS NOT NULL
|
|
AND requirements <> ''
|
|
LOOP
|
|
-- Split requirements into array
|
|
req_array := string_to_array(v_rel.requirements, ' | ');
|
|
|
|
-- Create edge for each requirement
|
|
IF req_array IS NOT NULL THEN
|
|
FOREACH req IN ARRAY req_array LOOP
|
|
IF req <> '' THEN
|
|
-- Only include edges where both nodes match filter criteria
|
|
IF schema_filter IS NULL OR split_part(req, '.', 1) = schema_filter THEN
|
|
result_edges := result_edges || ' "' || v_rel.relation || '" -> "' || req || '";' || E'\n';
|
|
END IF;
|
|
END IF;
|
|
END LOOP;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
-- Combine nodes and edges
|
|
result := result || result_nodes || result_edges || '}';
|
|
RETURN result;
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_export_dependency_graph(text, int, boolean) IS 'Exports dependency graph in DOT format for visualization with Graphviz';
|
|
|
|
-- Schema change simulation
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_simulate_schema_change(
|
|
p_schema_name text,
|
|
p_new_schema_name text DEFAULT NULL::text)
|
|
RETURNS TABLE(object_name text, obj_type text, affected_dependencies integer, affected_objects text[])
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
ROWS 1000
|
|
|
|
AS $BODY$
|
|
BEGIN
|
|
RETURN QUERY
|
|
SELECT
|
|
relation AS object_name,
|
|
dm.object_type AS obj_type,
|
|
deps::int AS affected_dependencies,
|
|
array_agg(dep_obj) AS affected_objects
|
|
FROM
|
|
public.c77_dep_dependencies_map_with_foreign dm
|
|
CROSS JOIN LATERAL unnest(
|
|
CASE WHEN dm.dependents <> ''
|
|
THEN string_to_array(dm.dependents, ' | ')
|
|
ELSE '{}'::text[]
|
|
END
|
|
) AS dep_obj
|
|
WHERE
|
|
split_part(relation, '.', 1) = p_schema_name
|
|
GROUP BY
|
|
relation, dm.object_type, deps
|
|
ORDER BY
|
|
deps DESC;
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_simulate_schema_change(text, text) IS 'Simulates the impact of changing a schema name';
|
|
|
|
-- Migration Order Function
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_generate_migration_order(
|
|
)
|
|
RETURNS TABLE(migration_phase integer, objects_to_migrate text[], object_count integer)
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
ROWS 1000
|
|
|
|
AS $BODY$
|
|
DECLARE
|
|
remaining_objects text[];
|
|
current_level integer := 0;
|
|
objects_at_level text[];
|
|
current_object text;
|
|
current_req text;
|
|
req_array text[];
|
|
all_requirements_met boolean;
|
|
placed_objects text[] := '{}';
|
|
progress_made boolean;
|
|
BEGIN
|
|
-- Get all objects
|
|
SELECT array_agg(relation)
|
|
INTO remaining_objects
|
|
FROM public.c77_dep_dependencies_map_with_foreign;
|
|
|
|
-- Keep processing until all objects are placed or no progress is made
|
|
WHILE array_length(remaining_objects, 1) > 0 LOOP
|
|
objects_at_level := '{}';
|
|
progress_made := false;
|
|
|
|
-- Find objects for current level
|
|
FOR i IN 1..array_length(remaining_objects, 1) LOOP
|
|
current_object := remaining_objects[i];
|
|
|
|
-- Get requirements for this object
|
|
SELECT string_to_array(COALESCE(requirements, ''), ' | ')
|
|
INTO req_array
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE relation = current_object;
|
|
|
|
-- Check if all requirements are already placed or empty
|
|
all_requirements_met := true;
|
|
|
|
-- Empty requirements can go in first level
|
|
IF req_array IS NULL OR array_length(req_array, 1) IS NULL OR req_array = '{}' THEN
|
|
all_requirements_met := true;
|
|
ELSE
|
|
-- Check each requirement
|
|
FOREACH current_req IN ARRAY req_array LOOP
|
|
IF current_req <> '' AND NOT current_req = ANY(placed_objects) THEN
|
|
all_requirements_met := false;
|
|
EXIT;
|
|
END IF;
|
|
END LOOP;
|
|
END IF;
|
|
|
|
-- If all requirements met, add to current level
|
|
IF all_requirements_met THEN
|
|
objects_at_level := array_append(objects_at_level, current_object);
|
|
placed_objects := array_append(placed_objects, current_object);
|
|
progress_made := true;
|
|
END IF;
|
|
END LOOP;
|
|
|
|
-- If we placed objects in this level, return the results
|
|
IF array_length(objects_at_level, 1) > 0 THEN
|
|
migration_phase := current_level;
|
|
objects_to_migrate := objects_at_level;
|
|
object_count := array_length(objects_at_level, 1);
|
|
RETURN NEXT;
|
|
|
|
-- Remove placed objects from remaining set
|
|
SELECT array_agg(o)
|
|
INTO remaining_objects
|
|
FROM unnest(remaining_objects) AS o
|
|
WHERE NOT o = ANY(objects_at_level);
|
|
END IF;
|
|
|
|
-- Break if no progress was made (might be due to circular dependencies)
|
|
IF NOT progress_made THEN
|
|
-- Add remaining objects to a final level (these likely have circular dependencies)
|
|
IF array_length(remaining_objects, 1) > 0 THEN
|
|
migration_phase := current_level + 1;
|
|
objects_to_migrate := remaining_objects;
|
|
object_count := array_length(remaining_objects, 1);
|
|
RETURN NEXT;
|
|
remaining_objects := '{}';
|
|
END IF;
|
|
END IF;
|
|
|
|
current_level := current_level + 1;
|
|
END LOOP;
|
|
|
|
RETURN;
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_generate_migration_order() IS 'Generates a migration order for objects based on their dependencies';
|
|
|
|
-- Function to generate a database dependency report
|
|
CREATE OR REPLACE FUNCTION public.c77_dep_generate_report(
|
|
p_schema_name text DEFAULT NULL::text,
|
|
p_include_orphans boolean DEFAULT true,
|
|
p_include_circular boolean DEFAULT true)
|
|
RETURNS text
|
|
LANGUAGE 'plpgsql'
|
|
COST 100
|
|
VOLATILE PARALLEL UNSAFE
|
|
AS $BODY$
|
|
DECLARE
|
|
v_report text := '';
|
|
v_schema_count integer;
|
|
v_object_count integer;
|
|
v_dependency_count integer;
|
|
v_orphan_count integer;
|
|
v_circular_count integer;
|
|
v_hub_objects record;
|
|
v_circular_deps record;
|
|
BEGIN
|
|
-- Report header
|
|
v_report := v_report || '=========================================' || E'\n';
|
|
v_report := v_report || 'DATABASE DEPENDENCY ANALYSIS REPORT' || E'\n';
|
|
v_report := v_report || '=========================================' || E'\n\n';
|
|
v_report := v_report || 'Generated on: ' || now() || E'\n\n';
|
|
|
|
-- Filter criteria
|
|
IF p_schema_name IS NOT NULL THEN
|
|
v_report := v_report || 'Schema filter: ' || p_schema_name || E'\n\n';
|
|
END IF;
|
|
|
|
-- Summary statistics
|
|
SELECT
|
|
count(DISTINCT split_part(relation, '.', 1)),
|
|
count(*),
|
|
sum(deps)
|
|
INTO
|
|
v_schema_count,
|
|
v_object_count,
|
|
v_dependency_count
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name;
|
|
|
|
v_report := v_report || 'SUMMARY STATISTICS:' || E'\n';
|
|
v_report := v_report || '-------------------' || E'\n';
|
|
v_report := v_report || 'Total schemas: ' || v_schema_count || E'\n';
|
|
v_report := v_report || 'Total objects: ' || v_object_count || E'\n';
|
|
v_report := v_report || 'Total dependencies: ' || v_dependency_count || E'\n\n';
|
|
|
|
-- Object type distribution
|
|
v_report := v_report || 'OBJECT TYPE DISTRIBUTION:' || E'\n';
|
|
v_report := v_report || '------------------------' || E'\n';
|
|
FOR v_hub_objects IN
|
|
SELECT
|
|
object_type,
|
|
count(*) AS cnt,
|
|
round(100.0 * count(*) / v_object_count, 1) AS pct
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name
|
|
GROUP BY object_type
|
|
ORDER BY count(*) DESC
|
|
LOOP
|
|
v_report := v_report || v_hub_objects.object_type || ': ' ||
|
|
v_hub_objects.cnt || ' (' ||
|
|
v_hub_objects.pct || '%)' || E'\n';
|
|
END LOOP;
|
|
v_report := v_report || E'\n';
|
|
|
|
-- Top hub objects
|
|
v_report := v_report || 'TOP DEPENDENCY HUBS:' || E'\n';
|
|
v_report := v_report || '-------------------' || E'\n';
|
|
FOR v_hub_objects IN
|
|
SELECT
|
|
relation,
|
|
object_type,
|
|
deps AS direct_deps,
|
|
add_deps AS indirect_deps
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE (p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name)
|
|
ORDER BY deps + add_deps DESC
|
|
LIMIT 10
|
|
LOOP
|
|
v_report := v_report || v_hub_objects.relation || ' (' || v_hub_objects.object_type ||
|
|
'): Direct deps: ' || v_hub_objects.direct_deps ||
|
|
', Indirect: ' || v_hub_objects.indirect_deps || E'\n';
|
|
END LOOP;
|
|
v_report := v_report || E'\n';
|
|
|
|
-- Orphaned objects
|
|
IF p_include_orphans THEN
|
|
SELECT count(*) INTO v_orphan_count
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE deps = 0
|
|
AND (p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name);
|
|
|
|
v_report := v_report || 'ORPHANED OBJECTS (' || v_orphan_count || '):' || E'\n';
|
|
v_report := v_report || '-------------------' || E'\n';
|
|
|
|
IF v_orphan_count > 0 THEN
|
|
FOR v_hub_objects IN
|
|
SELECT relation, object_type
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE deps = 0
|
|
AND (p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name)
|
|
ORDER BY relation
|
|
LIMIT 20
|
|
LOOP
|
|
v_report := v_report || v_hub_objects.relation || ' (' || v_hub_objects.object_type || ')' || E'\n';
|
|
END LOOP;
|
|
|
|
IF v_orphan_count > 20 THEN
|
|
v_report := v_report || '... and ' || (v_orphan_count - 20) || ' more' || E'\n';
|
|
END IF;
|
|
ELSE
|
|
v_report := v_report || 'No orphaned objects found.' || E'\n';
|
|
END IF;
|
|
v_report := v_report || E'\n';
|
|
END IF;
|
|
|
|
-- Circular dependencies section - using the dedicated function
|
|
IF p_include_circular THEN
|
|
-- Count circular dependencies
|
|
SELECT count(*) INTO v_circular_count
|
|
FROM public.c77_dep_detect_circular_dependencies() cd
|
|
WHERE p_schema_name IS NULL OR split_part(cd.object1, '.', 1) = p_schema_name;
|
|
|
|
v_report := v_report || 'CIRCULAR DEPENDENCIES (' || v_circular_count || '):' || E'\n';
|
|
v_report := v_report || '-------------------------' || E'\n';
|
|
|
|
IF v_circular_count > 0 THEN
|
|
-- List circular dependencies
|
|
FOR v_circular_deps IN
|
|
SELECT object1, object2, dependency_type
|
|
FROM public.c77_dep_detect_circular_dependencies()
|
|
WHERE p_schema_name IS NULL OR split_part(object1, '.', 1) = p_schema_name
|
|
ORDER BY dependency_type, object1, object2
|
|
LIMIT 20
|
|
LOOP
|
|
v_report := v_report || v_circular_deps.object1 || ' <-> ' ||
|
|
v_circular_deps.object2 || ' (' ||
|
|
v_circular_deps.dependency_type || ')' || E'\n';
|
|
END LOOP;
|
|
|
|
IF v_circular_count > 20 THEN
|
|
v_report := v_report || '... and ' || (v_circular_count - 20) || ' more' || E'\n';
|
|
END IF;
|
|
ELSE
|
|
v_report := v_report || 'No circular dependencies found.' || E'\n';
|
|
END IF;
|
|
v_report := v_report || E'\n';
|
|
END IF;
|
|
|
|
|
|
-- Recommendations
|
|
v_report := v_report || 'RECOMMENDATIONS:' || E'\n';
|
|
v_report := v_report || '----------------' || E'\n';
|
|
|
|
-- Recommendation for schema refactoring if there are many objects in one schema
|
|
IF EXISTS (
|
|
SELECT 1
|
|
FROM (
|
|
SELECT
|
|
split_part(relation, '.', 1) AS schema_name,
|
|
count(*) AS object_count
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name
|
|
GROUP BY split_part(relation, '.', 1)
|
|
) AS schema_counts
|
|
WHERE object_count > 100
|
|
) THEN
|
|
v_report := v_report || '- Consider schema refactoring for large schemas (>100 objects)' || E'\n';
|
|
END IF;
|
|
|
|
-- Recommendation for circular dependencies
|
|
IF v_circular_count > 0 THEN
|
|
v_report := v_report || '- Resolve circular dependencies to improve database maintainability' || E'\n';
|
|
END IF;
|
|
|
|
-- Recommendation for orphaned objects
|
|
IF v_orphan_count > 0 THEN
|
|
v_report := v_report || '- Review orphaned objects for potential removal or cleanup' || E'\n';
|
|
END IF;
|
|
|
|
-- Recommendation for dependency hubs
|
|
IF EXISTS (
|
|
SELECT 1
|
|
FROM public.c77_dep_dependencies_map_with_foreign
|
|
WHERE deps > 15 AND (p_schema_name IS NULL OR split_part(relation, '.', 1) = p_schema_name)
|
|
) THEN
|
|
v_report := v_report || '- Consider refactoring objects with many dependencies (>15)' || E'\n';
|
|
END IF;
|
|
|
|
RETURN v_report;
|
|
END;
|
|
$BODY$;
|
|
|
|
COMMENT ON FUNCTION public.c77_dep_generate_report(text, boolean, boolean) IS 'Generates a comprehensive dependency analysis report for the database';
|
|
|
|
-- Extension setup completion
|
|
DO $$
|
|
BEGIN
|
|
RAISE NOTICE 'c77_dep extension version 1.0.0 has been successfully installed.';
|
|
RAISE NOTICE 'Use the following views and functions to analyze database dependencies:';
|
|
RAISE NOTICE '- c77_dep_dependencies_map - Base dependency map';
|
|
RAISE NOTICE '- c77_dep_dependencies_map_with_foreign - Enhanced dependency map with foreign tables';
|
|
RAISE NOTICE '- c77_dep_orphaned_objects - Objects with no dependencies';
|
|
RAISE NOTICE '- c77_dep_schema_complexity - Schema complexity metrics';
|
|
RAISE NOTICE '- c77_dep_object_type_summary - Summary of object types';
|
|
RAISE NOTICE '- c77_dep_risk_assessment - Risk assessment for schema changes';
|
|
RAISE NOTICE '- c77_dep_hub_objects - Objects with many dependencies';
|
|
RAISE NOTICE '- c77_dep_cleanup_candidates - Objects that might need cleanup';
|
|
RAISE NOTICE '- c77_dep_analyze_drop_impact() - Analyze impact of dropping an object';
|
|
RAISE NOTICE '- c77_dep_detect_circular_dependencies() - Detect circular dependencies';
|
|
RAISE NOTICE '- c77_dep_export_dependency_graph() - Export dependencies in DOT format';
|
|
RAISE NOTICE '- c77_dep_simulate_schema_change() - Simulate schema rename impact';
|
|
RAISE NOTICE '- c77_dep_generate_migration_order() - Generate suggested migration order';
|
|
RAISE NOTICE '- c77_dep_generate_report() - Generate dependency analysis report';
|
|
END
|
|
$$; |