Re: Referential cascade technique - Mailing list pgsql-general

From Mike Finn
Subject Re: Referential cascade technique
Date
Msg-id 01072416212600.01056@birch.tacticalExecutive.com
Whole thread Raw
In response to Re: Referential cascade technique  ("Jim Buttafuoco" <jim@spectrumtelecorp.com>)
List pgsql-general
> I use the following PLPERL/select "code" to view all FK's in my database
> ..  I guess the "select" could be made into a pg_fkeys view.  What do
> people think...

I think what you've done is quite good.  I have had to adapt what you've done
since I really don't want to use perl.  I want to minimize the dependencies
the the db has so I've opted to figure out how to do it in pgplsql (and yes
those byte arrays a pain).

One assumption I have made is that the foreign key references are all single
field (not compound key).  This works in my system since this whole issue is
really for code tables, where we migrate the actual data to the referring
table so as to avoid a ton of joins for simple little code lookups.

I am including the whole shebang here in case it is of use to others, or
simple as a non-trivial example of plpgsql (which I have just had a crash
course in!).

-- a view to display foreign key references
-- BUG: assumes that all fk relationships are sigle key
drop view foreignKeyReference;
create view foreignKeyReference as
    select
      --field order is 0. procName
      --               1. referringTable
      --               2. localTable
      --               3. UNSPECIFIED
      --               4. referringField
      --               5. localField
      proc.proname as proc,
      trig.tgnargs = 6 as isSingleFieldKey,
      extractParmFromBytea(trig.tgargs, 0) as name,
      extractParmFromBytea(trig.tgargs, 2) as tableName,
      extractParmFromBytea(trig.tgargs, 5) as fieldName,
      extractParmFromBytea(trig.tgargs, 1) as referringTable,
      extractParmFromBytea(trig.tgargs, 4) as referringField
    from
       pg_class    class,
       pg_trigger  trig,
       pg_proc     proc
    where
         class.relname not like 'pg_%' and class.relkind = 'r'
     and trig.tgrelid = class.oid
     and trig.tgisconstraint = true
     and trig.tgfoid = proc.oid
     --and proc.proname like 'RI_FKey%_del'
    order by
        class.relname, trig.tgname
    ;


-- Get the Nth parm from a parameter set stored as null delimited
-- bytea as a string.  Parameters are numbered from 0
drop function extractParmFromBytea(bytea, int);
create function extractParmFromBytea(bytea, int) returns text as '
    declare
        bparm   alias for $1;  --bytea parameters
        parmIdx alias for $2;

        parmCount  int = 0;
        charCount  int = 0;
        c          int;
        parm       text = '''';

    begin
        while parmCount < parmIdx loop
            c := get_byte(bparm, charCount);
            charCount := charCount + 1;
            if c = 0 then
                parmCount := parmCount + 1;
            end if;
        end loop;

        while parmCount = parmIdx loop
            c := get_byte(bparm, charCount);
            charCount := charCount + 1;
            if c = 0 then
                parmCount := parmCount + 1;
            else
                parm := parm || chr(c);
            end if;
        end loop;

        return parm;
    end;
' language 'plpgsql';


-- For a given table (localTable) and field (localField) which are known
-- to be referred to via one or more foregin key relationships, update all
-- the dependant foreign references from oldValue to newValue.  This will
-- effectively move the dependencies from one record in localTable to another
-- record in localTable.
-- BUG: assumes that all fk relationships are sigle key
drop   function moveDependants(text,text,text,text);
create function moveDependants(text,text,text,text) returns boolean as '
    declare
        --parameters
        localTable alias for $1;
        localField alias for $2;
        oldValue   alias for $3;
        newValue   alias for $4;

        --translation from bytea to parms
        referringTable text;
        referringField text;

        --main part
        query      text;
        fks        record;
        rc         int;


    begin
        --lock the source oldValue exclusively
        query := '' select null''
              || '' from ''
              ||      localTable
              || '' where ''
          ||      localField || '' = '' || quote_literal(oldValue)
          || '' for update ''
              ;
        execute query;

        --lock the source newValue exclusively
        query := '' select null''
              || '' from ''
              ||      localTable
              || '' where ''
          ||      localField || '' = '' || quote_literal(newValue)
          || '' for update ''
              ;
        execute query;

        get diagnostics rc = ROW_COUNT;
        if rc <= 0 then
            raise exception ''newValue of % does not exist in %.%'',
newValue, localTable, localField;
        end if;

        --find the parameters for the del triggers
        query := '' select ''
              || ''   tgargs ''
              || '' from ''
              || ''   pg_class    class, ''
              || ''   pg_trigger  trig,  ''
              || ''   pg_proc     proc   ''
              || '' where ''
              || ''       class.relname = '' ||
quote_literal(lower(localTable))
              || ''   and class.relkind = ''''r'''' ''
              || ''   and trig.tgrelid = class.oid ''
              || ''   and trig.tgisconstraint = true ''
              || ''   and trig.tgfoid = proc.oid ''
              || ''   and proc.proname like ''''RI_FKey%_del'''' ''
              ;

        --field order is 0. procName
        --               1. referringTable
        --               2. localTable
        --               3. UNSPECIFIED
        --               4. referringField
        --               5. localField
        for fks in execute query loop
            if lower(localField) = extractParmFromBytea(fks.tgargs, 5) then
                --okay this reference is for localTable and localField
                --so update the referring values from oldValue to newValue
                query := '' update ''
                      ||      extractParmFromBytea(fks.tgargs, 1)
                      || '' set ''
                      ||      extractParmFromBytea(fks.tgargs, 4) || '' =''
|| quote_literal(newValue)
                      || '' where ''
                      ||      extractParmFromBytea(fks.tgargs, 4) || '' =''
|| quote_literal(oldValue)
                      ;
                --raise notice ''processing %'', query;
                execute query;
            end if;
        end loop;
        return true; --bogus return value
    end;
' language 'plpgsql';

-- an example
-- believe it or not the postal abbreviation for  Quebec
-- is actually 'QC'... its true, go figure!
/*
begin
    select moveDependants('codeProvinceState','code','PQ','QC');
    delete from codeProvinceState
        where code = 'Q.C.';
commit;
*/



===================
Mike Finn
Tactical Executive Systems
mike.finn@tacticalExecutive.com

pgsql-general by date:

Previous
From: Joseph Shraibman
Date:
Subject: Re: Bad timestamp external representation
Next
From: R Talbot
Date:
Subject: Re: Postgres User and Password