-- -------------------------------------------------------------------------- -- -- PostgreSQL Extended Inheritance Library -- -- -- This library contains a set of functions that extend PostgreSQL's table -- inheritance in order to work around some of the model's limitations: it -- propagates indexes, constraints, foreign keys and triggers, ensures -- uniqueness for primary and unique key in the whole hierarchy, and allows -- foreign keys referencing tables with children to reference both elements -- of the referenced tables AND elements defined in child tables. -- -- -- A DeepClone Development/Nocternity project -- Copyright(C) 2007, E. Benoit -- -- -------------------------------------------------------------------------- -- -- Create the inheritance schema in which the various system tables -- and related functions will reside. -- CREATE SCHEMA inheritance; -- -------------------------------------------------------------------------- -- PUBLIC FUNCTIONS -- -------------------------------------------------------------------------- -- -- Function that adds a table to be ignored -- CREATE OR REPLACE FUNCTION inheritance.ignore_table(NAME, NAME) RETURNS BOOLEAN AS $$ BEGIN INSERT INTO inheritance.ignore(ign_schema,ign_table) VALUES($1, $2); RETURN TRUE; EXCEPTION WHEN unique_violation THEN RETURN FALSE; END; $$ LANGUAGE plpgsql; -- -- Adds a schema to be handled to the list of schemas -- CREATE OR REPLACE FUNCTION inheritance.add_schema(NAME) RETURNS BOOLEAN AS $$ BEGIN INSERT INTO inheritance.schemas (sch_name) VALUES ($1); RETURN TRUE; EXCEPTION WHEN unique_violation THEN RETURN FALSE; END; $$ LANGUAGE plpgsql; -- -- Register a foreign key that can't be handled using built-in -- SQL because of indexes not being propagated automatically. -- -- Usage: SELECT inheritance.foreign_key('"namespace".src_table', '"field1",field2', 'namespace."dst_table"', 'dest1,dest2', 'CASCADE', 'SET NULL'); -- CREATE OR REPLACE FUNCTION inheritance.foreign_key(on_table TEXT, src_fields TEXT, to_table TEXT, dst_fields TEXT, on_update TEXT, on_delete TEXT) RETURNS VOID AS $$ DECLARE f_ns NAME; f_tb NAME; f_fl NAME[]; t_ns NAME; t_tb NAME; t_fl NAME[]; BEGIN SELECT INTO f_ns, f_tb * FROM inheritance.split_table_name(on_table); SELECT INTO t_ns, t_tb * FROM inheritance.split_table_name(to_table); SELECT INTO f_fl * FROM inheritance.split_field_list(src_fields); SELECT INTO t_fl * FROM inheritance.split_field_list(dst_fields); INSERT INTO inheritance.manual_fkeys(mfk_from_ns,mfk_from_table,mfk_from_fields,mfk_to_ns,mfk_to_table,mfk_to_fields,mfk_on_update,mfk_on_delete) VALUES (f_ns, f_tb, f_fl, t_ns, t_tb, t_fl, upper(on_update), upper(on_delete)); END; $$ LANGUAGE plpgsql; -- -- Function that handles the whole thing -- CREATE OR REPLACE FUNCTION inheritance.init() RETURNS VOID AS $$ BEGIN -- Destroy any existing data PERFORM inheritance.kill(); -- Update the table cache PERFORM inheritance.update_table_cache(); -- Read existing constraints, indexes and triggers on tables PERFORM inheritance.cache_initial_triggers(); PERFORM inheritance.cache_initial_indexes(); PERFORM inheritance.cache_initial_constraints(); -- Propagate constraints, indexes and triggers PERFORM inheritance.propagate(); -- Handle foreign keys PERFORM inheritance.handle_foreign_keys(); END; $$ LANGUAGE plpgsql; -- -- Function that removes everything the inheritance code did previously -- This function should be called before proceeding to any modifications -- on a database's existing structure or before updating the inheritance -- system. -- CREATE OR REPLACE FUNCTION inheritance.kill() RETURNS VOID AS $$ BEGIN -- Flush foreign keys DELETE FROM inheritance.fk_cache; -- Flush the trigger cache DELETE FROM inheritance.tgr_cache; -- Destroy the constraint cache DELETE FROM inheritance.con_cache; -- Destroy the index cache DELETE FROM inheritance.idx_cache; -- Destroy the inheritance cache DELETE FROM inheritance.tbl_cache; -- Destroy primary key / unique key handlers PERFORM inheritance.destroy_unique_functions(); END; $$ LANGUAGE plpgsql; -- -------------------------------------------------------------------------- -- -------------------------------------------------------------------------- -- TABLES USED INTERNALLY FOR CACHING PURPOSES -- -------------------------------------------------------------------------- -- -- Create the table that lists tables to be ignored -- CREATE TABLE inheritance.ignore ( ign_schema NAME NOT NULL, ign_table NAME NOT NULL, ign_oid OID, PRIMARY KEY(ign_schema,ign_table) ); -- -- Table that lists the schemas to be considered when handling inherited tables -- CREATE TABLE inheritance.schemas ( sch_name NAME PRIMARY KEY ); -- -- Table that will contain the inheritance cache -- CREATE TABLE inheritance.tbl_cache ( tch_parent OID NOT NULL, tch_child OID NOT NULL, tch_direct BOOLEAN NOT NULL, PRIMARY KEY(tch_parent,tch_child,tch_direct) ); -- -- Table that will cache constraints on tables the inheritance code handles -- CREATE TABLE inheritance.con_cache ( cch_table OID NOT NULL, cch_constraint OID NOT NULL, cch_inherited BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY(cch_table, cch_constraint) ); -- -- Table that will list functions created in order to handle primary keys -- or unique keys -- CREATE TABLE inheritance.ufn_cache ( uch_fname NAME NOT NULL PRIMARY KEY ); -- -- Table that will list indexes that have been propagated from base tables; -- it also lists indexes that are present from the beginning with the -- ich_inherited field set to FALSE. -- CREATE TABLE inheritance.idx_cache ( ich_table OID NOT NULL, ich_index OID NOT NULL, ich_inherited BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY(ich_table, ich_index) ); -- -- Table that will list both initial triggers and triggers added by the -- inheritance code. -- CREATE TABLE inheritance.tgr_cache ( tch_table OID NOT NULL, tch_trigger OID NOT NULL, tch_initial BOOLEAN NOT NULL DEFAULT TRUE, PRIMARY KEY(tch_table, tch_trigger) ); -- -- Table that stores the list of supported foreign key actions -- CREATE TABLE inheritance.fk_actions ( txt VARCHAR(9) NOT NULL UNIQUE, ch CHAR NOT NULL UNIQUE ); COPY inheritance.fk_actions FROM STDIN; CASCADE c SET NULL n NO ACTION a \. -- -- Table to store "foreign keys" to child tables which can't be created -- with the REFERENCES keyword due to the fact that child tables have -- no indexes when they are created (unless these indexes are added -- manually, which we don't want). -- CREATE TABLE inheritance.manual_fkeys ( mfk_from_ns NAME NOT NULL, mfk_from_table NAME NOT NULL, mfk_from_fields NAME[] NOT NULL CHECK(mfk_from_fields <> '{}'), mfk_to_ns NAME NOT NULL, mfk_to_table NAME NOT NULL, mfk_to_fields NAME[] NOT NULL CHECK(mfk_to_fields <> '{}'), mfk_on_update VARCHAR(9) NOT NULL REFERENCES inheritance.fk_actions (txt), mfk_on_delete VARCHAR(9) NOT NULL REFERENCES inheritance.fk_actions (txt), PRIMARY KEY(mfk_from_ns,mfk_from_table,mfk_from_fields), CHECK(array_dims(mfk_from_fields) = array_dims(mfk_to_fields)) ); -- -- Table to store foreign keys that have been removed because they were -- referencing base tables and were replaced by sets of triggers, and -- "manual" foreign keys. -- CREATE TABLE inheritance.fk_cache ( fkc_from_oid OID NOT NULL, fkc_from_attr INT2[] NOT NULL, fkc_to_oid OID NOT NULL, fkc_to_attr INT2[] NOT NULL, fkc_on_update CHAR NOT NULL REFERENCES inheritance.fk_actions (ch), fkc_on_delete CHAR NOT NULL REFERENCES inheritance.fk_actions (ch), fkc_manual BOOLEAN NOT NULL DEFAULT FALSE, fkc_constraint OID, fkc_create TEXT, PRIMARY KEY(fkc_from_oid,fkc_from_attr) ); -- -------------------------------------------------------------------------- -- -------------------------------------------------------------------------- -- INTERNAL FUNCTIONS -- -------------------------------------------------------------------------- -- -- Function that looks up a table's OID using its namespace and table name -- CREATE OR REPLACE FUNCTION inheritance.get_table_oid(namespace NAME, tablename NAME) RETURNS OID STABLE AS $$ SELECT c.oid FROM pg_class c, pg_namespace n WHERE c.relname = $2 AND n.nspname = $1 AND c.relnamespace = n.oid $$ LANGUAGE SQL; -- -- Function that looks up a table's name and namespace using its OID -- CREATE OR REPLACE FUNCTION inheritance.get_table_name(tableoid OID, OUT namespace NAME, OUT tablename NAME) STABLE AS $$ SELECT n.nspname, c.relname FROM pg_class c, pg_namespace n WHERE c.oid = $1 AND n.oid = c.relnamespace $$ LANGUAGE SQL; -- -- Function that looks up the names of a set of attributes -- CREATE OR REPLACE FUNCTION inheritance.get_attr_names(tableoid OID, attrids INT2[]) RETURNS NAME[] STABLE AS $$ DECLARE tmp NAME; attr NAME[]; i INT; BEGIN attr := '{}'; FOR i IN array_lower(attrids, 1) .. array_upper(attrids, 1) LOOP SELECT INTO tmp attname FROM pg_attribute WHERE attrelid = tableoid AND attnum = attrids[i]; IF NOT FOUND THEN RETURN NULL; END IF; attr[i] := tmp; END LOOP; RETURN attr; END; $$ LANGUAGE plpgsql; -- -- Function that sets the OIDs in the list of tables to be ignored -- CREATE OR REPLACE FUNCTION inheritance.update_ignore_oids() RETURNS VOID AS $$ DECLARE rec RECORD; rd_oid OID; BEGIN FOR rec IN SELECT ign_schema, ign_table FROM inheritance.ignore WHERE ign_oid IS NULL LOOP rd_oid := inheritance.get_table_oid(rec.ign_schema, rec.ign_table); IF rd_oid IS NOT NULL THEN UPDATE inheritance.ignore SET ign_oid=rd_oid WHERE ign_schema=rec.ign_schema AND ign_table=rec.ign_table; END IF; END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that stores a table's inheritance data -- CREATE OR REPLACE FUNCTION inheritance.add_to_cache(parent OID, child OID) RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN -- Generate indirect inheritance FOR rec IN SELECT tch_parent FROM inheritance.tbl_cache WHERE tch_child=parent LOOP INSERT INTO inheritance.tbl_cache(tch_parent,tch_child,tch_direct) VALUES (rec.tch_parent, child, FALSE); END LOOP; -- Generate direct inheritance INSERT INTO inheritance.tbl_cache(tch_parent,tch_child,tch_direct) VALUES (parent, child, TRUE); END; $$ LANGUAGE plpgsql; -- -- Function that looks up a table's children and stores them in the cache -- CREATE OR REPLACE FUNCTION inheritance.find_children(parent OID) RETURNS VOID AS $$ DECLARE child RECORD; BEGIN FOR child IN SELECT c.oid FROM pg_class c, pg_inherits i WHERE c.oid = i.inhrelid AND i.inhparent = parent AND c.oid NOT IN (SELECT ign_oid FROM inheritance.ignore WHERE ign_oid IS NOT NULL) LOOP -- Add to cache PERFORM inheritance.add_to_cache(parent, child.oid); -- Find child tables PERFORM inheritance.find_children(child.oid); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that will update the inheritance cache -- CREATE OR REPLACE FUNCTION inheritance.update_table_cache() RETURNS VOID AS $$ DECLARE base RECORD; BEGIN -- Make sure the ignore list is up-to-date PERFORM inheritance.update_ignore_oids(); -- Find tables that do not inherit from any other table, are not set to be ignored and -- are in the list of schemas we are supposed to examine FOR base IN SELECT oid FROM pg_class WHERE relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname IN (SELECT sch_name FROM inheritance.schemas)) AND oid NOT IN (SELECT ign_oid FROM inheritance.ignore WHERE ign_oid IS NOT NULL) AND oid NOT IN (SELECT inhrelid FROM pg_inherits) AND relhassubclass LOOP PERFORM inheritance.find_children(base.oid); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that drops a constraint using its OID -- CREATE OR REPLACE FUNCTION inheritance.drop_constraint(cst OID) RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN SELECT INTO rec c.conname AS cn, r.relname AS tn, n.nspname AS nn FROM pg_constraint c, pg_class r, pg_namespace n WHERE c.oid = cst AND r.oid = c.conrelid AND n.oid = r.relnamespace; IF FOUND THEN EXECUTE 'ALTER TABLE "' || rec.nn || '"."' || rec.tn || '" DROP CONSTRAINT "' || rec.cn || '"'; END IF; END; $$ LANGUAGE plpgsql; -- -- Function that drops a trigger using its OID -- CREATE OR REPLACE FUNCTION inheritance.drop_trigger(tg OID) RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN SELECT INTO rec t.tgname AS n, r.relname AS tn, n.nspname AS nn FROM pg_trigger t, pg_class r, pg_namespace n WHERE t.oid = tg AND r.oid = t.tgrelid AND n.oid = r.relnamespace; IF FOUND THEN EXECUTE 'DROP TRIGGER "' || rec.n || '" ON "' || rec.nn || '"."' || rec.tn || '"'; -- RAISE NOTICE 'DROP TRIGGER "%" ON "%"."%"', rec.n, rec.nn, rec.tn; END IF; END; $$ LANGUAGE plpgsql; -- -- Function that drops an index using its OID -- CREATE OR REPLACE FUNCTION inheritance.drop_index(idx OID) RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN SELECT INTO rec * FROM inheritance.get_table_name(idx); IF rec.namespace IS NOT NULL THEN EXECUTE 'DROP INDEX "' || rec.namespace || '"."' || rec.tablename || '"'; END IF; END; $$ LANGUAGE plpgsql; -- -- Trigger function that drops constraints added by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.drop_inherited_constraints() RETURNS TRIGGER AS $$ BEGIN IF OLD.cch_inherited THEN PERFORM inheritance.drop_constraint(OLD.cch_constraint); END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER con_cache_drop_inherited AFTER DELETE ON inheritance.con_cache FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_constraints(); -- -- Trigger function that drops triggers added by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.drop_inherited_triggers() RETURNS TRIGGER AS $$ BEGIN IF NOT OLD.tch_initial THEN PERFORM inheritance.drop_trigger(OLD.tch_trigger); END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER tgr_cache_drop_inherited AFTER DELETE ON inheritance.tgr_cache FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_triggers(); -- -- Trigger function that drops indexes added by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.drop_inherited_indexes() RETURNS TRIGGER AS $$ BEGIN IF OLD.ich_inherited THEN PERFORM inheritance.drop_index(OLD.ich_index); END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER idx_cache_drop_inherited AFTER DELETE ON inheritance.idx_cache FOR EACH ROW EXECUTE PROCEDURE inheritance.drop_inherited_indexes(); -- -- Function that looks up a table's original constrains and stores them in -- the constraint cache -- CREATE OR REPLACE FUNCTION inheritance.cache_table_constraints(tbl OID) RETURNS VOID AS $$ BEGIN INSERT INTO inheritance.con_cache (cch_table, cch_constraint) SELECT tbl,c.oid FROM pg_constraint c WHERE c.conrelid = tbl; END; $$ LANGUAGE plpgsql; -- -- Function that caches indexes for all existing tables handled by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.cache_initial_indexes() RETURNS VOID AS $$ BEGIN INSERT INTO inheritance.idx_cache (ich_table, ich_index) SELECT indrelid,indexrelid FROM pg_index WHERE indrelid IN ( SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache ); END; $$ LANGUAGE plpgsql; -- -- Function that caches triggers for all existing tables handled by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.cache_initial_triggers() RETURNS VOID AS $$ BEGIN INSERT INTO inheritance.tgr_cache (tch_table, tch_trigger) SELECT tgrelid,oid FROM pg_trigger WHERE tgrelid IN ( SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache ) AND NOT tgisconstraint; END; $$ LANGUAGE plpgsql; -- -- Function that caches constraints for all existing tables handled by the inheritance code -- CREATE OR REPLACE FUNCTION inheritance.cache_initial_constraints() RETURNS VOID AS $$ BEGIN INSERT INTO inheritance.con_cache (cch_table, cch_constraint) SELECT conrelid,oid FROM pg_constraint WHERE conrelid IN ( SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache ); END; $$ LANGUAGE plpgsql; -- -- Function that copies foreign key constraints from a table to another -- CREATE OR REPLACE FUNCTION inheritance.copy_fkey_constraints(src OID, dst OID) RETURNS VOID AS $$ DECLARE trec RECORD; rec RECORD; at TEXT; i INT; BEGIN -- Get the destination table's name and namespace name SELECT INTO trec * FROM inheritance.get_table_name(dst); -- Get the constraints to copy FOR rec IN SELECT oid FROM pg_constraint WHERE contype = 'f' AND oid IN ( SELECT cch_constraint FROM inheritance.con_cache WHERE cch_table=src AND NOT cch_inherited) LOOP EXECUTE 'ALTER TABLE "' || trec.namespace || '"."' || trec.tablename || '" ADD ' || pg_get_constraintdef(rec.oid); END LOOP; -- Update the constraint cache INSERT INTO inheritance.con_cache (cch_table, cch_constraint, cch_inherited) SELECT dst,c.oid,TRUE FROM pg_constraint c WHERE c.conrelid = dst AND c.oid NOT IN ( SELECT cch_constraint FROM inheritance.con_cache WHERE cch_table=dst ); END; $$ LANGUAGE plpgsql; -- -- Function that generates a function (how perverse!) to check for a table's inherited -- primary key. -- CREATE OR REPLACE FUNCTION inheritance.make_key_function(fn NAME, tbl OID, fl SMALLINT[], kt TEXT) RETURNS VOID AS $$ DECLARE tdet RECORD; fdet RECORD; code TEXT; ncode TEXT; ocode TEXT; i INT; BEGIN SELECT INTO tdet * FROM inheritance.get_table_name(tbl); ncode := ''; ocode := ''; FOR i IN array_lower(fl, 1) .. array_upper(fl, 1) LOOP IF ocode <> '' THEN ocode := ocode || ' AND '; ncode := ncode || ' AND '; END IF; SELECT INTO fdet attname FROM pg_attribute WHERE attrelid=tbl AND attnum=fl[i]; ncode := ncode || '"' || fdet.attname || '"=NEW."' || fdet.attname || '"'; ocode := ocode || '"' || fdet.attname || '"<>OLD."' || fdet.attname || '"'; END LOOP; EXECUTE 'CREATE OR REPLACE FUNCTION inheritance.' || fn || '() RETURNS TRIGGER AS ''' || 'DECLARE c INT; ' || 'BEGIN ' || 'IF TG_OP=''''INSERT'''' THEN ' || 'SELECT INTO c COUNT(*) FROM "' || tdet.namespace || '"."' || tdet.tablename || '" WHERE ' || ncode || ';' || 'ELSE ' || 'SELECT INTO c COUNT(*) FROM "' || tdet.namespace || '"."' || tdet.tablename || '" WHERE ' || ncode || ' AND ' || ocode || ';' || 'END IF;' || 'IF FOUND AND c>0 THEN ' || 'RAISE EXCEPTION ''''Duplicate ' || kt || ' key value in table "%" inherited from "' || tdet.namespace || '"."' || tdet.tablename || '"'''', TG_RELNAME;' || 'END IF;' || 'RETURN NEW;' || 'END;' || ''' LANGUAGE plpgsql'; INSERT INTO inheritance.ufn_cache(uch_fname) VALUES (fn); END; $$ LANGUAGE plpgsql; -- -- Function that propagates the base tables' primary keys -- CREATE OR REPLACE FUNCTION inheritance.propagate_primary_keys() RETURNS VOID AS $$ DECLARE pkey RECORD; ctbl RECORD; fn NAME; BEGIN -- Loop on all primary keys found for the base tables FOR pkey IN SELECT DISTINCT i.tch_parent AS t,c.conkey AS k FROM inheritance.tbl_cache i, pg_constraint c WHERE i.tch_parent NOT IN (SELECT DISTINCT tch_child FROM inheritance.tbl_cache) AND c.conrelid=i.tch_parent AND c.contype='p' LOOP -- Generate the function that checks for this specific primary key fn := 'pkey_check_' || pkey.t; PERFORM inheritance.make_key_function(fn, pkey.t, pkey.k, 'primary'); -- Create the trigger on the table itself SELECT INTO ctbl * FROM inheritance.get_table_name(pkey.t); EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' || 'ON "' || ctbl.namespace || '"."' || ctbl.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, pkey.t, FALSE FROM pg_trigger WHERE tgname = fn AND tgrelid = pkey.t; -- Create the trigger for all tables that inherit the base FOR ctbl IN SELECT r.relname AS tn, n.nspname AS nn, r.oid AS o FROM inheritance.tbl_cache i, pg_class r, pg_namespace n WHERE i.tch_parent = pkey.t AND r.oid = i.tch_child AND n.oid = r.relnamespace LOOP EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' || 'ON "' || ctbl.nn || '"."' || ctbl.tn || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT t.oid, ctbl.o, FALSE FROM pg_trigger t WHERE t.tgname = fn AND tgrelid = ctbl.o; END LOOP; END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that propagates the tables' unique keys -- CREATE OR REPLACE FUNCTION inheritance.propagate_unique_keys() RETURNS VOID AS $$ DECLARE ukey RECORD; ctbl RECORD; fn NAME; BEGIN -- Loop on all primary keys found for the base tables FOR ukey IN SELECT DISTINCT i.tch_parent AS t,c.oid AS c,c.conkey AS k FROM inheritance.tbl_cache i, pg_constraint c WHERE c.conrelid=i.tch_parent AND c.contype='u' LOOP -- Generate the function that checks for this specific key fn := 'ukey_check_' || ukey.c; PERFORM inheritance.make_key_function(fn, ukey.t, ukey.k, 'unique'); -- Create the trigger on the table itself SELECT INTO ctbl * FROM inheritance.get_table_name(ukey.t); EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' || 'ON "' || ctbl.namespace || '"."' || ctbl.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, ukey.t, FALSE FROM pg_trigger WHERE tgname = fn AND tgrelid = ukey.t; -- Create the trigger for all tables that inherit the base FOR ctbl IN SELECT r.relname AS tn, n.nspname AS nn, r.oid AS o FROM inheritance.tbl_cache i, pg_class r, pg_namespace n WHERE i.tch_parent = ukey.t AND r.oid = i.tch_child AND n.oid = r.relnamespace LOOP EXECUTE 'CREATE TRIGGER ' || fn || ' BEFORE INSERT OR UPDATE ' || 'ON "' || ctbl.nn || '"."' || ctbl.tn || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.' || fn || '()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, ctbl.o, FALSE FROM pg_trigger WHERE tgname = fn AND tgrelid = ctbl.o; END LOOP; END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that duplicates an index -- CREATE OR REPLACE FUNCTION inheritance.duplicate_index(idx OID, tbl OID) RETURNS VOID AS $$ DECLARE irec RECORD; trec RECORD; attr RECORD; fl TEXT; i INT; BEGIN SELECT INTO irec CASE indisunique WHEN TRUE THEN ' UNIQUE' ELSE '' END AS unq, indkey::int2[] AS fls, indrelid FROM pg_index WHERE indexrelid = idx; SELECT INTO trec * FROM inheritance.get_table_name(tbl); fl := ''; FOR i IN array_lower(irec.fls, 1) .. array_upper(irec.fls, 1) LOOP IF fl <> '' THEN fl := fl || ','; END IF; SELECT INTO attr attname FROM pg_attribute WHERE attrelid = irec.indrelid AND attnum = irec.fls[i]; fl := fl || '"' || attr.attname || '"'; END LOOP; EXECUTE 'CREATE' || irec.unq || ' INDEX inh_idx_dup_' || idx || '_' || tbl || ' ON "' || trec.namespace || '"."' || trec.tablename || '" (' || fl || ')'; END; $$ LANGUAGE plpgsql; -- -- Function that propagates indexes from tables to their children -- CREATE OR REPLACE FUNCTION inheritance.propagate_indexes() RETURNS VOID AS $$ DECLARE idx RECORD; BEGIN FOR idx IN SELECT i.ich_index, t.tch_child FROM inheritance.idx_cache i, inheritance.tbl_cache t WHERE NOT i.ich_inherited AND t.tch_parent = i.ich_table LOOP PERFORM inheritance.duplicate_index(idx.ich_index, idx.tch_child); END LOOP; INSERT INTO inheritance.idx_cache (ich_table, ich_index, ich_inherited) SELECT indrelid,indexrelid,TRUE FROM pg_index WHERE indrelid IN ( SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache ) AND indexrelid NOT IN (SELECT ich_index FROM inheritance.idx_cache WHERE NOT ich_inherited); END; $$ LANGUAGE plpgsql; -- -- Function that copies all of the base tables' constraints to their children -- CREATE OR REPLACE FUNCTION inheritance.propagate() RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN -- Propagate indexes PERFORM inheritance.propagate_indexes(); -- Propagate the base tables' primary keys PERFORM inheritance.propagate_primary_keys(); -- Propagate the tables' unique keys PERFORM inheritance.propagate_unique_keys(); -- Propagate triggers PERFORM inheritance.propagate_triggers(); -- Copy CHECK and FOREIGN KEY constraints FOR rec IN SELECT tch_parent, tch_child FROM inheritance.tbl_cache LOOP PERFORM inheritance.copy_fkey_constraints(rec.tch_parent, rec.tch_child); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that deletes functions used for primary key and unique key -- enforcement. -- CREATE OR REPLACE FUNCTION inheritance.destroy_unique_functions() RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN FOR rec IN SELECT * FROM inheritance.ufn_cache LOOP EXECUTE 'DROP FUNCTION inheritance."' || rec.uch_fname || '"()'; -- CASCADE'; END LOOP; DELETE FROM inheritance.ufn_cache; END; $$ LANGUAGE plpgsql; -- -- Function that propagates triggers from base tables to their children -- CREATE OR REPLACE FUNCTION inheritance.propagate_triggers() RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN FOR rec IN SELECT t.tch_trigger AS tg, i.tch_child AS tb FROM inheritance.tgr_cache t, inheritance.tbl_cache i WHERE i.tch_parent = t.tch_table AND t.tch_initial LOOP PERFORM inheritance.copy_trigger(rec.tg, rec.tb); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that copies a trigger to some table -- CREATE OR REPLACE FUNCTION inheritance.copy_trigger(tg OID, tbl OID) RETURNS VOID AS $$ DECLARE tbr RECORD; -- Table record tgr RECORD; -- Trigger record prr RECORD; -- Procedure record code TEXT; need_or BOOLEAN; astring BYTEA; BEGIN -- Get trigger, table and procedure data SELECT INTO tbr * FROM inheritance.get_table_name(tbl); SELECT INTO tgr tgfoid AS fo, tgtype::int::bit(5) AS t, tgargs AS a FROM pg_trigger WHERE oid = tg; SELECT INTO prr p.proname AS pn, n.nspname AS nn FROM pg_proc p, pg_namespace n WHERE p.oid = tgr.fo AND n.oid = p.pronamespace; -- Generate the beginning of the CREATE TRIGGER instruction code := 'CREATE TRIGGER tgr_dup_' || tg || '_' || tbl || ' ' || (CASE (tgr.t << 3)::bit(1) WHEN B'1' THEN 'BEFORE' ELSE 'AFTER' END) || ' '; -- Check the operations to which the trigger applies need_or := FALSE; IF (tgr.t << 2)::bit(1) = B'1' THEN code := code || 'INSERT '; need_or := TRUE; END IF; IF (tgr.t << 1)::bit(1) = B'1' THEN code := code || (CASE need_or WHEN TRUE THEN 'OR ' ELSE '' END) || 'DELETE '; need_or := TRUE; END IF; IF tgr.t::bit(1) = B'1' THEN code := code || (CASE need_or WHEN TRUE THEN 'OR ' ELSE '' END) || 'UPDATE '; END IF; -- Add table name, FOR EACH thingy and procedure name code := code || 'ON "' || tbr.namespace || '"."' || tbr.tablename || '" FOR EACH ' || (CASE (tgr.t << 4)::bit(1) WHEN B'1' THEN 'ROW' ELSE 'STATEMENT' END) || ' EXECUTE PROCEDURE "' || prr.nn || '"."' || prr.pn || '"('; -- Generate the arguments list astring := tgr.a; need_or := FALSE; WHILE position('\\000'::bytea IN astring) > 0 LOOP IF need_or THEN code := code || ','; ELSE need_or := TRUE; END IF; code := code || quote_literal(encode(substring(astring FROM 1 FOR position('\\000'::bytea IN astring) - 1), 'escape')); astring := substring(astring FROM position('\\000'::bytea IN astring) + 1); END LOOP; code := code || ')'; -- Create the trigger and add it to the list EXECUTE code; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, tbl, FALSE FROM pg_trigger WHERE tgname = 'tgr_dup_' || tg || '_' || tbl AND tgrelid = tbl; END; $$ LANGUAGE plpgsql; -- -- This function splits a string containing a namespace and table name into -- two strings, the namespace's name and the table's name. -- CREATE OR REPLACE FUNCTION inheritance.split_table_name(complete TEXT, OUT nsname NAME, OUT tblname NAME) AS $$ DECLARE s TEXT; BEGIN s := split_part(complete, '.', 1); IF substr(s, 1, 1) = '"' THEN nsname := substr(s, 2, char_length(s) - 2); ELSE nsname := lower(s); END IF; s := split_part(complete, '.', 2); IF substr(s, 1, 1) = '"' THEN tblname := substr(s, 2, char_length(s) - 2); ELSE tblname := lower(s); END IF; END; $$ LANGUAGE plpgsql; -- -- This function splits a string of coma-separated field names into an array -- of names. -- CREATE OR REPLACE FUNCTION inheritance.split_field_list(fstr TEXT, OUT lst NAME[]) AS $$ DECLARE s TEXT; i INT; BEGIN lst := '{}'; i := 1; LOOP s := split_part(fstr, ',', i); IF s = '' THEN EXIT; END IF; IF substr(s, 1, 1) = '"' THEN lst[i] := substr(s, 2, char_length(s) - 2); ELSE lst[i] := lower(s); END IF; i := i + 1; END LOOP; END; $$ LANGUAGE plpgsql; -- -- This function handles foreign keys (be them "real" SQL foreign keys or manual -- foreign keys created by inheritance.foreign_key()). -- CREATE OR REPLACE FUNCTION inheritance.handle_foreign_keys() RETURNS VOID AS $$ BEGIN -- Cache real SQL foreign keys PERFORM inheritance.cache_real_fkeys(); -- Cache manual foreign keys PERFORM inheritance.cache_manual_fkeys(); -- Create the functions and triggers PERFORM inheritance.make_fkey_triggers(); END; $$ LANGUAGE plpgsql; -- -- This function reads the list of real foreign keys that refer to tables -- handled by the inheritance code. If the foreign keys have been added by -- inheritance, it removes them silently and stores them into the cache as -- if they were "manual" foreign keys -- CREATE OR REPLACE FUNCTION inheritance.cache_real_fkeys() RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN -- List the foreign keys INSERT INTO inheritance.fk_cache (fkc_from_oid,fkc_from_attr,fkc_to_oid,fkc_to_attr,fkc_constraint,fkc_on_update,fkc_on_delete,fkc_manual) SELECT c.conrelid, c.conkey, c.confrelid, c.confkey, c.oid, c.confupdtype, c.confdeltype, ( SELECT COUNT(*)=1 FROM inheritance.con_cache WHERE cch_table=c.conrelid AND cch_inherited='t' AND cch_constraint=c.oid) FROM pg_constraint c WHERE c.contype='f' AND c.confrelid IN ( SELECT DISTINCT tch_parent AS tid FROM inheritance.tbl_cache UNION SELECT DISTINCT tch_child AS tid FROM inheritance.tbl_cache); -- Drop constraints that were copied by the inheritance code DELETE FROM inheritance.con_cache WHERE cch_constraint IN ( SELECT fkc_constraint FROM inheritance.fk_cache WHERE fkc_manual); DELETE FROM inheritance.fk_cache WHERE fkc_manual; -- Drop the other constraints FOR rec IN SELECT fkc_constraint FROM inheritance.fk_cache WHERE NOT fkc_manual LOOP UPDATE inheritance.fk_cache SET fkc_create = pg_get_constraintdef(rec.fkc_constraint) WHERE fkc_constraint = rec.fkc_constraint; PERFORM inheritance.drop_constraint(rec.fkc_constraint); END LOOP; END; $$ LANGUAGE plpgsql; -- -- This function fetches the list of manual foreign keys and adds them to -- the cache if possible AND necessary. -- CREATE OR REPLACE FUNCTION inheritance.cache_manual_fkeys() RETURNS VOID AS $$ DECLARE mfk RECORD; stbl OID; dtbl OID; sfld INT2[]; dfld INT2[]; tmp INT2; i INT; BEGIN <> FOR mfk IN SELECT * FROM inheritance.manual_fkeys LOOP -- Get the source table's OID stbl := inheritance.get_table_oid(mfk.mfk_from_ns, mfk.mfk_from_table); CONTINUE WHEN stbl IS NULL; -- Get the destination table's OID dtbl := inheritance.get_table_oid(mfk.mfk_to_ns, mfk.mfk_to_table); CONTINUE WHEN dtbl IS NULL; -- Get the list of fields sfld := '{}'; dfld := '{}'; FOR i IN array_lower(mfk.mfk_from_fields, 1) .. array_upper(mfk.mfk_from_fields, 1) LOOP SELECT INTO tmp attnum FROM pg_attribute WHERE attrelid = stbl AND attname = mfk.mfk_from_fields[i]; CONTINUE cmfk_main_loop WHEN NOT FOUND; sfld[i] := tmp; SELECT INTO tmp attnum FROM pg_attribute WHERE attrelid = dtbl AND attname = mfk.mfk_to_fields[i]; CONTINUE cmfk_main_loop WHEN NOT FOUND; dfld[i] := tmp; END LOOP; -- Insert data INSERT INTO inheritance.fk_cache (fkc_from_oid,fkc_from_attr,fkc_to_oid,fkc_to_attr,fkc_manual,fkc_on_update,fkc_on_delete) VALUES (stbl, sfld, dtbl, dfld, TRUE, (SELECT ch FROM inheritance.fk_actions WHERE txt=mfk.mfk_on_update), (SELECT ch FROM inheritance.fk_actions WHERE txt=mfk.mfk_on_delete) ); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Trigger function that restore foreign keys that had been deleted -- CREATE OR REPLACE FUNCTION inheritance.restore_deleted_fkeys() RETURNS TRIGGER AS $$ DECLARE rec RECORD; BEGIN IF NOT OLD.fkc_manual THEN SELECT INTO rec * FROM inheritance.get_table_name(OLD.fkc_from_oid); EXECUTE 'ALTER TABLE "' || rec.namespace || '"."' || rec.tablename || '" ADD ' || OLD.fkc_create; END IF; RETURN OLD; END; $$ LANGUAGE plpgsql; CREATE TRIGGER fk_cache_restore_deleted AFTER DELETE ON inheritance.fk_cache FOR EACH ROW EXECUTE PROCEDURE inheritance.restore_deleted_fkeys(); -- -- Function that will generate trigger functions that will be applied -- before deletion or update on referenced tables and their children. -- CREATE OR REPLACE FUNCTION inheritance.make_referenced_functions(tbl OID) RETURNS VOID AS $$ DECLARE dcode TEXT; ucode TEXT; trec RECORD; trec2 RECORD; rec RECORD; rec2 RECORD; sattr NAME[]; dattr NAME[]; i INT; acode TEXT; BEGIN dcode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_del() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; ucode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_upd_d() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; SELECT INTO trec * FROM inheritance.get_table_name(tbl); -- For each set of referenced keys FOR rec IN SELECT DISTINCT fkc_to_attr AS attr FROM inheritance.fk_cache WHERE fkc_to_oid = tbl LOOP dattr := inheritance.get_attr_names(tbl, rec.attr); -- Generate condition for the UPDATE trigger ucode := ucode || 'IF '; FOR i IN array_lower(dattr, 1) .. array_upper(dattr, 1) LOOP ucode := ucode || 'NEW."' || dattr[i] || '" <> OLD."' || dattr[i] || '" '; IF i < array_upper(dattr, 1) THEN ucode := ucode || 'OR '; END IF; END LOOP; ucode := ucode || 'THEN '; -- For each referencing table FOR rec2 IN SELECT fkc_from_oid AS oid, fkc_from_attr AS attr, fkc_on_delete AS od, fkc_on_update AS ou FROM inheritance.fk_cache WHERE fkc_to_oid=tbl AND fkc_to_attr=rec.attr LOOP -- Fetch table and namespace SELECT INTO trec2 * FROM inheritance.get_table_name(rec2.oid); -- Fetch attribute names sattr := inheritance.get_attr_names(rec2.oid, rec2.attr); acode := ''; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP acode := acode || '"' || sattr[i] || '"=OLD."' || dattr[i] || '"'; IF i < array_upper(sattr, 1) THEN acode := acode || ' AND '; END IF; END LOOP; -- Handle "ON DELETE CASCADE" IF rec2.od = 'c' THEN dcode := dcode || 'DELETE FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode || ';'; -- Handle "ON DELETE SET NULL" ELSIF rec2.od = 'n' THEN dcode := dcode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP dcode := dcode || '"' || sattr[i] || '"=NULL'; IF i < array_upper(rec2.attr, 1) THEN dcode := dcode || ','; END IF; END LOOP; dcode := dcode || ' WHERE ' || acode || ';'; -- Handle "ON DELETE NO ACTION" ELSIF rec2.od = 'a' THEN dcode := dcode || 'SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode || '; IF c > 0 THEN RAISE EXCEPTION ''Foreign key on "' || trec2.namespace || '"."' || trec2.tablename || '" failed while deleting from "' || trec.namespace || '"."' || trec.tablename || '"''; END IF;'; END IF; -- Handle "ON UPDATE CASCADE" IF rec2.ou = 'c' THEN ucode := ucode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP ucode := ucode || '"' || sattr[i] || '"=NEW."' || dattr[i] || '"'; IF i < array_upper(rec2.attr, 1) THEN ucode := ucode || ','; END IF; END LOOP; ucode := ucode || ' WHERE ' || acode || ';'; -- Handle "ON UPDATE SET NULL" ELSIF rec2.ou = 'n' THEN ucode := ucode || 'UPDATE "' || trec2.namespace || '"."' || trec2.tablename || '" SET '; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP ucode := ucode || '"' || sattr[i] || '"=NULL'; IF i < array_upper(rec2.attr, 1) THEN ucode := ucode || ','; END IF; END LOOP; ucode := ucode || ' WHERE ' || acode || ';'; -- Handle "ON UPDATE NO ACTION" ELSIF rec2.ou = 'a' THEN ucode := ucode || 'SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE ' || acode || '; IF c > 0 THEN RAISE EXCEPTION ''Foreign key on "' || trec2.namespace || '"."' || trec2.tablename || '" failed while updating from "' || trec.namespace || '"."' || trec.tablename || '"''; END IF;'; END IF; END LOOP; ucode := ucode || 'END IF;'; END LOOP; dcode := dcode || 'RETURN OLD;END;$a$ LANGUAGE plpgsql'; ucode := ucode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; EXECUTE dcode; EXECUTE ucode; END; $$ LANGUAGE plpgsql; -- -- Function that will generate the trigger functions to be applied -- before insertion or update on referencing tables. -- CREATE OR REPLACE FUNCTION inheritance.make_referencing_functions(tbl OID) RETURNS VOID AS $$ DECLARE icode TEXT; ucode TEXT; rec RECORD; trec RECORD; trec2 RECORD; sattr NAME[]; dattr NAME[]; i INT; BEGIN icode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_ins() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; ucode := 'CREATE FUNCTION inheritance.tgr_fk_' || tbl || '_upd_s() RETURNS TRIGGER AS $a$DECLARE c INT;BEGIN '; SELECT INTO trec * FROM inheritance.get_table_name(tbl); -- For each set of referencing keys FOR rec IN SELECT fkc_from_attr AS attr, fkc_to_oid AS ttbl, fkc_to_attr AS tattr FROM inheritance.fk_cache WHERE fkc_from_oid = tbl LOOP sattr := inheritance.get_attr_names(tbl, rec.attr); -- Get referenced table and attribute SELECT INTO trec2 * FROM inheritance.get_table_name(rec.ttbl); dattr := inheritance.get_attr_names(rec.ttbl, rec.tattr); -- Create the code to be run on insertion icode := icode || 'IF'; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP icode := icode || ' NEW."' || sattr[i] || '" IS NOT NULL'; IF i <> array_upper(sattr, 1) THEN icode := icode || ' AND'; END IF; END LOOP; icode := icode || ' THEN SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE'; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP icode := icode || ' NEW."' || sattr[i] || '"="' || dattr[i] || '"'; IF i <> array_upper(sattr, 1) THEN icode := icode || ' AND'; END IF; END LOOP; icode := icode || '; IF c=0 THEN RAISE EXCEPTION ''Foreign key to "' || trec2.namespace || '"."' || trec2.tablename || '" failed while inserting into "' || trec.namespace || '"."' || trec.tablename || '"''; END IF; END IF;'; -- Create the code to be run on update ucode := ucode || 'IF ('; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP ucode := ucode || 'NEW."' || sattr[i] || '"<>OLD."' || sattr[i] || '"'; IF i <> array_upper(sattr, 1) THEN ucode := ucode || ' OR '; END IF; END LOOP; ucode := ucode || ') AND'; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP ucode := ucode || ' NEW."' || sattr[i] || '" IS NOT NULL'; IF i <> array_upper(sattr, 1) THEN ucode := ucode || ' AND'; END IF; END LOOP; ucode := ucode || ' THEN SELECT INTO c COUNT(*) FROM "' || trec2.namespace || '"."' || trec2.tablename || '" WHERE'; FOR i IN array_lower(sattr, 1) .. array_upper(sattr, 1) LOOP ucode := ucode || ' NEW."' || sattr[i] || '"="' || dattr[i] || '"'; IF i <> array_upper(sattr, 1) THEN ucode := ucode || ' AND'; END IF; END LOOP; ucode := ucode || '; IF c=0 THEN RAISE EXCEPTION ''Foreign key to "' || trec2.namespace || '"."' || trec2.tablename || '" failed while updating "' || trec.namespace || '"."' || trec.tablename || '"''; END IF; END IF;'; END LOOP; icode := icode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; ucode := ucode || 'RETURN NEW;END;$a$ LANGUAGE plpgsql'; EXECUTE icode; EXECUTE ucode; END; $$ LANGUAGE plpgsql; -- -- Function that will generate foreign key triggers -- CREATE OR REPLACE FUNCTION inheritance.make_fkey_triggers() RETURNS VOID AS $$ DECLARE rec RECORD; BEGIN -- Generate functions for all referencing tables FOR rec IN SELECT DISTINCT fkc_from_oid FROM inheritance.fk_cache LOOP PERFORM inheritance.make_referencing_functions(rec.fkc_from_oid); PERFORM inheritance.create_referencing_triggers(rec.fkc_from_oid); END LOOP; -- Generate functions and triggers for all referenced tables FOR rec IN SELECT DISTINCT fkc_to_oid FROM inheritance.fk_cache LOOP PERFORM inheritance.make_referenced_functions(rec.fkc_to_oid); PERFORM inheritance.create_referenced_triggers(rec.fkc_to_oid); END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that creates the foreign key triggers for referenced tables and their children -- CREATE OR REPLACE FUNCTION inheritance.create_referenced_triggers(dst OID) RETURNS VOID AS $$ DECLARE rec RECORD; trec RECORD; BEGIN -- Create triggers for the referenced table and its children FOR rec IN SELECT dst AS tid UNION SELECT tch_child AS tid FROM inheritance.tbl_cache WHERE tch_parent = dst LOOP SELECT INTO trec * FROM inheritance.get_table_name(rec.tid); EXECUTE 'CREATE TRIGGER tgr_fk_' || dst || '_del_' || rec.tid || ' BEFORE DELETE ON "' || trec.namespace || '"."' || trec.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || dst || '_del()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, rec.tid, FALSE FROM pg_trigger WHERE tgname = 'tgr_fk_' || dst || '_del_' || rec.tid AND tgrelid = rec.tid; EXECUTE 'CREATE TRIGGER tgr_fk_' || dst || '_updd_' || rec.tid || ' BEFORE UPDATE ON "' || trec.namespace || '"."' || trec.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || dst || '_upd_d()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, rec.tid, FALSE FROM pg_trigger WHERE tgname = 'tgr_fk_' || dst || '_updd_' || rec.tid AND tgrelid = rec.tid; END LOOP; END; $$ LANGUAGE plpgsql; -- -- Function that creates the foreign key triggers for referencing tables and their children -- CREATE OR REPLACE FUNCTION inheritance.create_referencing_triggers(src OID) RETURNS VOID AS $$ DECLARE rec RECORD; trec RECORD; BEGIN -- Create triggers for the referencing table and its children FOR rec IN SELECT src AS tid UNION SELECT tch_child AS tid FROM inheritance.tbl_cache WHERE tch_parent = src LOOP SELECT INTO trec * FROM inheritance.get_table_name(rec.tid); EXECUTE 'CREATE TRIGGER tgr_fk_' || src || '_ins_' || rec.tid || ' BEFORE INSERT ON "' || trec.namespace || '"."' || trec.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || src || '_ins()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, rec.tid, FALSE FROM pg_trigger WHERE tgname = 'tgr_fk_' || src || '_ins_' || rec.tid AND tgrelid = rec.tid; EXECUTE 'CREATE TRIGGER tgr_fk_' || src || '_upds_' || rec.tid || ' BEFORE UPDATE ON "' || trec.namespace || '"."' || trec.tablename || '" ' || 'FOR EACH ROW EXECUTE PROCEDURE inheritance.tgr_fk_' || src || '_upd_s()'; INSERT INTO inheritance.tgr_cache (tch_trigger, tch_table, tch_initial) SELECT oid, rec.tid, FALSE FROM pg_trigger WHERE tgname = 'tgr_fk_' || src || '_upds_' || rec.tid AND tgrelid = rec.tid; END LOOP; END; $$ LANGUAGE plpgsql;