--
-- $Id: pg_advisor.sql,v 1.13 2004/03/19 14:55:39 coelho Exp $
--
-- pg_advisor maybe future system schema?
--
-- at the time it is xpg_advisor as pg_ is reserved.
--

DROP SCHEMA xpg_advisor CASCADE;

CREATE SCHEMA xpg_advisor;

COMMENT ON SCHEMA xpg_advisor 
IS 'various advices about database design or performance' ;

SET search_path TO xpg_advisor,pg_catalog;

--
-- DESIGN ADVICES (da_*)
--

--
-- tables without primary keys
--

CREATE VIEW da_tables_without_primary_key AS 
SELECT n.nspname AS Schema, c.relname AS Name
FROM pg_class AS c 
  JOIN pg_namespace AS n ON (c.relnamespace=n.oid)
WHERE
  -- no comments about system catalogs.
  n.nspname NOT IN ('pg_catalog','pg_toast','information_schema','xpg_advisor')
  -- only tables
  AND c.relkind = 'r' 
  -- no primary key
  AND NOT c.relhaspkey
ORDER BY Schema ASC, Name ASC;

COMMENT ON VIEW da_tables_without_primary_key
IS 'it is better to have a primary key on your tables';

-- SELECT * FROM da_tables_without_primary_key;

--
-- tables with composite primary keys?
-- 

CREATE VIEW da_tables_with_composite_primary_key AS
SELECT n.nspname AS Schema, c.relname AS Name
FROM pg_class AS c 
  JOIN pg_namespace AS n ON (c.relnamespace=n.oid)
  JOIN pg_constraint AS o ON (o.conrelid=c.oid)
WHERE
  -- no comments about system catalogs.
  n.nspname NOT IN ('pg_catalog','pg_toast','information_schema','xpg_advisor')
  -- only tables
  AND c.relkind = 'r' 
  -- with a primary key
  AND c.relhaspkey
  -- the primary key constraint
  AND contype = 'p'
  -- more than 1 element in conkey
  AND array_upper(o.conkey, 1)>1
ORDER BY Schema ASC, Name ASC;

COMMENT ON VIEW da_tables_with_composite_primary_key
IS 'it may be considered a better design to have simple primary keys';

-- SELECT * FROM da_tables_with_composite_primary_key;

--
-- missing array_index function
--

CREATE OR REPLACE FUNCTION array_index(anyarray, anyelement) 
RETURNS INTEGER AS '
DECLARE
  tab ALIAS FOR $1;
  item ALIAS FOR $2;
  i INTEGER;
BEGIN
  i := 1;
  LOOP
    IF i > array_upper(tab, 1) THEN
	RETURN NULL;
    END IF;
    IF tab[i] = item THEN
	RETURN i;
    END IF;
    i := i+1;
  END LOOP;
END;'
LANGUAGE plpgsql;

COMMENT ON FUNCTION array_index(anyarray, anyelement)
IS 'missing array_index function... should be already there!';

--
-- internal foreign key constraint...
--
CREATE VIEW in_foreign_key_contraint AS
SELECT
  -- constraint
  c.oid AS constraint, 
  -- referencing table
  c.conrelid AS crel, 
  cca.attname AS ccol,
  cca.atttypid AS ctyp,
  cca.atttypmod AS cmod,
  cca.attlen AS clen,
  -- referenced table, foreign part
  c.confrelid AS frel, 
  fka.attname AS fcol, 
  fka.atttypid AS ftyp, 
  fka.atttypmod AS fmod,
  fka.attlen AS flen,
  array_index(c.confkey, fka.attnum) AS component
FROM pg_constraint AS c
     JOIN pg_attribute AS cca ON (c.conrelid=cca.attrelid)
     JOIN pg_attribute AS fka ON (c.confrelid=fka.attrelid)
WHERE
  -- foreign key constraint
  c.contype='f'
  -- column attribute in constraint
  AND cca.attnum = ANY (c.conkey)
  -- foreign key attribute
  AND fka.attnum = ANY (c.confkey)
  -- matching constraints
  AND array_index(c.confkey, fka.attnum)=array_index(c.conkey, cca.attnum)
;

--
-- foreign keys which do not exactly match their target key types?
--

CREATE VIEW da_foreign_key_type_dont_match AS
SELECT 
  nc.nspname AS Schema,
  cc.relname AS Name,
  fkc.ccol AS AttName,
  format_type(fkc.ctyp, fkc.cmod) AS ColumnType,
  nf.nspname AS FSchema,
  cf.relname AS FName,
  fkc.fcol AS FAttName,
  format_type(fkc.ftyp, fkc.fmod) AS ForeignType,
  fkc.component AS Component
FROM in_foreign_key_contraint AS fkc 
  JOIN pg_class AS cc ON (fkc.crel=cc.oid)
  JOIN pg_namespace AS nc ON (cc.relnamespace=nc.oid)
  --JOIN pg_attribute AS ac ON (fkc.ccol=ac.oid)
  JOIN pg_class AS cf ON (fkc.frel=cf.oid)
  JOIN pg_namespace AS nf ON (cf.relnamespace=nf.oid)
  --JOIN pg_attribute AS af ON (fkc.fcol=af.oid)
WHERE
  -- no comments about system catalogs.
  nc.nspname NOT IN('pg_catalog','pg_toast','information_schema','xpg_advisor')
  AND 
  nf.nspname NOT IN('pg_catalog','pg_toast','information_schema','xpg_advisor')
  -- only tables (redundant?)
  AND cc.relkind='r' AND cf.relkind='r'
  -- non matching type
  AND fkc.ctyp!=fkc.ftyp
ORDER BY Schema ASC, Name ASC, FSchema ASC, FName ASC, Component ASC;

COMMENT ON VIEW da_foreign_key_type_dont_match
IS 'non matching foreing key component, maybe a bad design';

-- SELECT * FROM da_foreign_key_type_dont_match;

--
-- others?
--
-- foreign keys which do match their target types but with different sizes?
-- tables with large primary keys (such as TEXT, VARCHAR(64)...)?

--
-- how to SELECT COUNT(*) FROM [the view of which I have the oid]?
-- here is the missing function, that may be somewhere I guess.

CREATE OR REPLACE FUNCTION count_tuples(OID) 
RETURNS INTEGER AS '
DECLARE
  oid ALIAS FOR $1;
  tablename TEXT;
  schemaname TEXT;
  res RECORD;
BEGIN
  -- get 
  SELECT INTO schemaname, tablename   n.nspname, c.relname
  FROM pg_class AS c JOIN pg_namespace AS n ON (c.relnamespace=n.oid)
  WHERE c.oid=oid;
  IF NOT FOUND THEN 
    RETURN NULL;
  END IF;
  FOR res IN
    EXECUTE \'SELECT COUNT(*) AS n FROM \' || schemaname || \'.\' || tablename
  LOOP
    RETURN res.n;
  END LOOP;
  RETURN NULL;
END;'
LANGUAGE 'plpgsql';

COMMENT ON FUNCTION count_tuples(OID) 
IS 'count number of rows in table or view, given its oid';

-- count summary of design advices?
CREATE VIEW design_advices_summary AS
SELECT
  count_tuples(c.oid) AS Count,
  c.relname AS Name, 
  d.description AS Description
FROM pg_class AS c
  JOIN pg_description AS d ON (d.objoid=c.oid)
  JOIN pg_namespace AS n ON (c.relnamespace=n.oid)
WHERE
  -- xpg_advisor views
  n.nspname = 'xpg_advisor'
  -- only design advices
  AND c.relname LIKE 'da_%'
ORDER BY Name ASC;

-- SELECT * FROM design_advices_summary;

--
-- PERFORMANCE ADVICES (pa_*)
--
-- no usable primary key index for foreign key referencial integrity checks?
-- no usable index for foreigh key on deletes?
--

-- count summary of performance advices?
