Hello!

       Every one.

       I met some difficult questions when I was developing an application.

       We have an old table object whose definition is:

              CREATE TABLE object (

                     name name, -- object’s name

                     pid oid,    -- parent’s object ID

                     PRIMARY KEY(oid),

                     FOREIGN KEY(pid)

REFERENCES object(oid)

ON DELETE SET NULL

              );

The table “object” is used as a base table and there are many tables(over 1500) that is derived from it for we treat almost every thing as object. Though the idea isn’t bad and it can really simplify our conceptual mode, it also results in low performance. When we want to find an object from table “object”, it will perform searching in 1500 tables, the long time to wait is insufferable. And the request that gives a pid to find childen oid is called frequently.

So we decide to reimplement the system and now we treat parent-child relationship is a common relationship. The relationship is named “containment”. For the “pid” field is wild used by other programmers, we can’t delete it. Then we have a task to keep the two table existent. To reduce the possibility to make mistakes, we define triggers on every table.

Now the system can work well when we do “insert”, “delete” or “update” operation. But when I drop a table that is inherits from “object” or the table “object” itself, the triggers on the table will not work. To avoid such cases, I wrote a function “_test_delete_and_drop()” that will delete all columns in table “object” and then drop the table. But I met puzzling information when I do that.

 

I have two questions:

       1. Why don’t the triggers work when drop a table?

       2. I can do “delete” and “drop” without any problem. Why do the error come out when I combine the two operation together?

Note: The source code is in the accessory(test.sql).

 

       Could any one help?

       Any effort will be appreciated.

       Thanks very much!



Do You Yahoo!?
嫌邮箱太小?雅虎电邮自助扩容!
/****************************************************************************
Function Description:
        _test_setup(): Initialize the environment
        _test_teardown(): Clean up
        _test_object_insert(): Insert records into "object"
        _test_object_update(): Update records of "object"
        _test_object_delete(): Delete records from "object"
        _test_containment_insert(): Insert records into "containment"
        _test_containmnet_delete(): Delete records from "containment"
        _trigger_update_containment(): The trigger on table "object" 
                that is used to update table "containment"
        _trigger_update_object(): The trigger on table "containment" 
                that is used to update table "object"
        _test_delete_and_drop(): Delete records from "object" and drop table 
"object"

Usage:
        Before running any test, you should reload test.sql use the command 
        "\i test.sql" for the function will be analysed and cached once it run. 
        If the table was deleted, there will be errors.
        See the results use the command "SELECCT * FROM object;" and 
        "SELECT * FROM containment" to examine the records in table "object"
        and table "containment".
        
        Test inserting records into table "object":
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT * FROM object;
        SELECT * FROM containment;
        
        Test updating table "object":
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT _test_object_update();
        SELECT * FROM object;
        SELECT * FROM containment;
        
        Test deleting records from "object":
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT _test_object_delete();
        SELECT * FROM object;
        SELECT * FROM containment;

        Test inserting records into table "containment":
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT _test_containment_insert();
        SELECT * FROM object;
        SELECT * FROM containment;
        
        Test deleting records from table "containment":
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT _test_containment_delete();
        SELECT * FROM object;
        SELECT * FROM containment;

!!!!!!  All above test cases can pass. But the following is an exception:
        \i test.sql;
        SELECT _test_setup();
        SELECT _test_object_insert();
        SELECT _test_delete_and_drop();

****************************************************************************/

-----------------------------------------------------------------------------
-- Tests
-----------------------------------------------------------------------------

-- Initialize the test environment
CREATE OR REPLACE FUNCTION _test_setup()
RETURNS void AS $$
BEGIN
        PERFORM _helper_drop_table('object');
        CREATE TABLE object
        (
                name name,
                pid oid
        );
        PERFORM _helper_drop_table('containment');
        CREATE TABLE containment
        (
                parent oid,
                child oid
        );
        CREATE TRIGGER _trigger_update_containment AFTER DELETE OR INSERT OR 
UPDATE ON object 
                FOR EACH ROW EXECUTE PROCEDURE _trigger_update_containment();
        CREATE TRIGGER _trigger_update_object AFTER DELETE OR INSERT ON 
containment 
                FOR EACH ROW EXECUTE PROCEDURE _trigger_update_object();        
        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Clean up
CREATE OR REPLACE FUNCTION _test_teardown()
RETURNS void AS $$
BEGIN
        DROP TABLE object;
        DROP TABLE containment;
        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Insert records into "object"
CREATE OR REPLACE FUNCTION _test_object_insert()
RETURNS void AS $$
DECLARE
        l_pid oid;
BEGIN
        INSERT INTO object(name) VALUES('obj1');
        GET DIAGNOSTICS l_pid = RESULT_OID;
        INSERT INTO object(name, pid) VALUES('obj11', l_pid);
        INSERT INTO object(name, pid) VALUES('obj12', l_pid);
        INSERT INTO object(name) VALUES('obj21');
        INSERT INTO object(name, pid) VALUES('obj22', l_pid);
        INSERT INTO object(name) VALUES('obj23');
        INSERT INTO object(name, pid) VALUES('obj24', l_pid);
        INSERT INTO object(name, pid) VALUES('obj25', l_pid);
        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Update records of "object"
CREATE OR REPLACE FUNCTION _test_object_update()
RETURNS void AS $$
DECLARE
        l_obj1 oid;
        l_obj21 oid;
BEGIN
        SELECT oid INTO l_obj1 FROM object WHERE name = 'obj1';
        SELECT oid INTO l_obj21 FROM object WHERE name = 'obj21';

        -- NEW.pid = OLD.pid = NULL
        UPDATE object SET pid = NULL WHERE name = 'obj21';

        -- NEW.pid = OLD.pid
        UPDATE object SET pid = l_obj1 WHERE name = 'obj22';

        -- OLD.pid = NULL AND NEW.pid <> NULL
        UPDATE object SET pid = l_obj21 WHERE name = 'obj23';

        -- OLD.pid <> NULL AND NEW.pid = NULL
        UPDATE object SET pid = NULL WHERE name = 'obj24';

        -- OLD.pid <> NULL AND NEW.pid <> NULL
        UPDATE object SET pid = l_obj21 WHERE name = 'obj25';

        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Delete records of "object"
CREATE OR REPLACE FUNCTION _test_object_delete()
RETURNS void AS $$
BEGIN
        DELETE FROM object WHERE name = 'obj1';
        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Delete records in "containment"
CREATE OR REPLACE FUNCTION _test_containment_delete()
RETURNS void AS $$
DECLARE
        l_obj12 oid;
BEGIN
        SELECT oid INTO l_obj12 FROM object WHERE name = 'obj12';
        DELETE FROM containment WHERE child = l_obj12;
        RETURN;
END;
$$ LANGUAGE plpgsql;

-- Insert records to "containment"
CREATE OR REPLACE FUNCTION _test_containment_insert()
RETURNS void AS $$
DECLARE
        l_obj11 oid;
        l_obj12 oid;
BEGIN
        SELECT oid INTO l_obj11 FROM object WHERE name = 'obj11';
        SELECT oid INTO l_obj12 FROM object WHERE name = 'obj12';
        INSERT INTO containment(parent, child) VALUES(l_obj11, l_obj12);
        RETURN;
END;
$$ LANGUAGE plpgsql;

--
-- A special test
-- 
CREATE OR REPLACE FUNCTION _test_delete_and_drop()
RETURNS void AS $$
BEGIN
        DELETE FROM object;
        DROP TABLE object;
        RETURN;
END;
$$ LANGUAGE plpgsql;

-----------------------------------------------------------------------------
-- Triggers
-----------------------------------------------------------------------------
-- The trigger on table "object" that is used to update table "containment"
CREATE OR REPLACE FUNCTION _trigger_update_containment()
RETURNS trigger AS $$
DECLARE
        l_rec RECORD;
BEGIN
        IF TG_OP = 'DELETE' THEN
                -- clear all descendants
                FOR l_rec IN SELECT child FROM containment WHERE parent = 
OLD.oid LOOP
                        DELETE FROM object WHERE oid = l_rec.child;
                END LOOP;
                -- delete corresponding record in table containment
                DELETE FROM containment WHERE child = OLD.oid;
        ELSIF TG_OP = 'INSERT' THEN
                -- add record in table containment
                IF NEW.pid NOTNULL THEN
                        INSERT INTO containment(parent, child) VALUES(NEW.pid, 
NEW.oid);
                END IF;         
        ELSIF TG_OP = 'UPDATE' THEN
                -- NEW is equral to OLD
                IF (NEW.pid ISNULL AND OLD.pid ISNULL) OR NEW.pid = OLD.pid THEN
                        RETURN null;
                END IF;
        
                IF OLD.pid ISNULL THEN
                        IF NOT EXISTS(SELECT * FROM containment WHERE child = 
NEW.oid) THEN
                                INSERT INTO containment(parent, child) 
VALUES(NEW.pid, NEW.oid);
                        END IF;
                ELSE
                        IF NEW.pid IS NULL THEN
                                DELETE FROM containment WHERE child = NEW.oid;
                        ELSE
                                UPDATE containment SET parent = NEW.pid WHERE 
child = NEW.oid;
                        END IF;
                END IF;                 
        END IF;
        RETURN null;
END;
$$ LANGUAGE plpgsql;

-- The trigger on table "containment" that is used to update table "object"
CREATE OR REPLACE FUNCTION _trigger_update_object()
RETURNS trigger AS $$
BEGIN
        IF TG_OP = 'DELETE' THEN
                UPDATE object SET pid = null WHERE oid = OLD.child;
        ELSIF TG_OP = 'INSERT' THEN
                UPDATE object SET pid = NEW.parent WHERE oid = NEW.child;
        END IF;
        RETURN null;
END;
$$ LANGUAGE plpgsql;

-----------------------------------------------------------------------------
-- Helper functions
-----------------------------------------------------------------------------
/*
Judge a table exists or not
*/
CREATE OR REPLACE FUNCTION _helper_is_table_existent(name)
RETURNS boolean AS $$
BEGIN
        RETURN EXISTS(SELECT * FROM pg_class WHERE relname = $1);
END;
$$ LANGUAGE plpgsql;

/*
Drop a table and its descendants by name
*/
CREATE OR REPLACE FUNCTION _helper_drop_table(name)
RETURNS void AS $$
BEGIN
        IF _helper_is_table_existent($1)
        THEN
                EXECUTE 'DROP TABLE ' || quote_ident($1) || ' CASCADE';
        END IF;
        RETURN;
END;
$$ LANGUAGE plpgsql;
---------------------------(end of broadcast)---------------------------
TIP 6: Have you searched our list archives?

               http://archives.postgresql.org

Reply via email to