-- **************************************************************** -- ** DROP THE LOT -- **************************************************************** DROP TABLE "purchaseorder_line" CASCADE; DROP SEQUENCE "purchaseorder_line_seq" CASCADE; DROP TABLE "purchaseorder_line_status" CASCADE; DROP TABLE "purchaseorder" CASCADE; DROP SEQUENCE "purchaseorder_seq" CASCADE; DROP TABLE "purchaseorder_status" CASCADE; DROP FUNCTION po_stat(varchar) CASCADE; DROP FUNCTION pol_stat(varchar) CASCADE; DROP FUNCTION spawn_pol(int, int); DROP TYPE spawned_pol_type; DROP FUNCTION tr_purchaseorder_def() CASCADE; DROP LANGUAGE 'plpgsql' CASCADE; DROP FUNCTION plpgsql_call_handler() CASCADE; CREATE FUNCTION plpgsql_call_handler() RETURNS LANGUAGE_HANDLER AS '$libdir/plpgsql' LANGUAGE C; -- **************************************************************** -- ** CREATE THE LOT -- **************************************************************** CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql HANDLER plpgsql_call_handler; CREATE TABLE "purchaseorder_status" ( "id" int primary key check(id > 0), "abbreviation" varchar(20) not null unique, "descr" varchar(30) not null ); COPY "purchaseorder_status" FROM stdin; 1 PO_ONE one 2 PO_TWO two 3 PO_THREE three 4 PO_FOUR four \. CREATE SEQUENCE purchaseorder_seq start 3; CREATE TABLE "purchaseorder" ( "id" int primary key check(id > 0), "status_id" int not null references purchaseorder_status(id), "price_total" numeric not null check(price_total >= 0) ); COPY "purchaseorder" FROM stdin; 1 1 0 2 1 0 \. CREATE TABLE "purchaseorder_line_status" ( "id" int primary key check(id > 0), "abbreviation" varchar(20) not null unique, "descr" varchar(30) not null ); COPY "purchaseorder_line_status" FROM stdin; 1 POL_ONE one 2 POL_TWO two 3 POL_THREE three 4 POL_FOUR four \. CREATE SEQUENCE purchaseorder_line_seq start 6; CREATE TABLE "purchaseorder_line" ( "id" int primary key check(id > 0), "purchaseorder_id" int not null references purchaseorder(id), "status_id" int not null references purchaseorder_line_status(id), "amount" int not null check(amount > 0), "price" numeric check(price > 0) ); COPY "purchaseorder_line" FROM stdin; 1 1 1 99 9.99 2 1 3 99 9.99 3 2 1 99 9.99 4 2 2 99 9.99 5 2 4 99 9.99 \. CREATE OR REPLACE FUNCTION po_stat(varchar) RETURNS int LANGUAGE 'sql' IMMUTABLE STRICT SECURITY INVOKER AS ' SELECT id FROM purchaseorder_status WHERE abbreviation = $1'; CREATE OR REPLACE FUNCTION pol_stat(varchar) RETURNS int LANGUAGE 'sql' IMMUTABLE STRICT SECURITY INVOKER AS ' SELECT id FROM purchaseorder_line_status WHERE abbreviation = $1'; CREATE TYPE spawned_pol_type AS (pol_id_orig int, pol_id_new int); CREATE OR REPLACE FUNCTION spawn_pol(integer, integer) RETURNS SETOF spawned_pol_type LANGUAGE 'plpgsql' VOLATILE STRICT SECURITY INVOKER AS ' DECLARE my_id ALIAS FOR $1; spawned_amount ALIAS FOR $2; result_size INTEGER; pol RECORD; result spawned_pol_type%ROWTYPE; BEGIN -- select the orderline to spawn SELECT into pol * FROM purchaseorder_line WHERE id = my_id FOR UPDATE; result.pol_id_orig = my_id; -- our amount must be high enough to be able to spawn IF spawned_amount >= pol.amount THEN RAISE EXCEPTION ''The original amount % in line % is not high enough to be able to spawn amount %'', pol.amount, spawned_amount, my_id; RETURN; END IF; -- we have enough stock, insert the new orderline and update the original one INSERT INTO purchaseorder_line SELECT nextval(''purchaseorder_line_seq''), -- id purchaseorder_id, status_id, spawned_amount, -- amount, price FROM purchaseorder_line WHERE id = my_id; result.pol_id_new = currval(''purchaseorder_line_seq''); UPDATE purchaseorder_line SET amount = amount - spawned_amount WHERE id = my_id; -- finish RETURN NEXT result; RETURN; END;'; CREATE or replace FUNCTION tr_purchaseorder_def() RETURNS trigger LANGUAGE 'plpgsql' VOLATILE STRICT SECURITY INVOKER AS ' DECLARE my_purchaseorder_id INTEGER := 0; BEGIN -- how are we called? IF TG_OP = ''INSERT'' THEN my_purchaseorder_id = NEW.purchaseorder_id; ELSIF TG_OP = ''UPDATE'' THEN my_purchaseorder_id = NEW.purchaseorder_id; ELSE -- TG_OP = ''DELETE'' my_purchaseorder_id = OLD.purchaseorder_id; END IF; -- why are we processing? RAISE NOTICE ''tr_purchaseorder_def() was triggered by the % on % for id %'', TG_OP, TG_RELNAME, my_purchaseorder_id; -- update the purchaseorder UPDATE purchaseorder SET price_total = total, status_id = CASE WHEN count_lines = 0 OR count_pol_one > 0 THEN po_stat(''PO_ONE'') WHEN count_pol_two > 0 THEN po_stat(''PO_TWO'') WHEN count_pol_three > 0 THEN po_stat(''PO_THREE'') WHEN count_pol_four = count_lines THEN po_stat(''PO_FOUR'') ELSE po_stat(''PO_FOUR'') END FROM ( SELECT purchaseorder_id, coalesce(SUM(price * amount), 0) AS total, COUNT(purchaseorder_line.id) AS count_lines, COUNT(CASE WHEN status_id = pol_stat(''POL_ONE'') THEN 1 ELSE null END) AS count_pol_one, COUNT(CASE WHEN status_id = pol_stat(''POL_TWO'') THEN 1 ELSE null END) AS count_pol_two, COUNT(CASE WHEN status_id = pol_stat(''POL_THREE'') THEN 1 ELSE null END) AS count_pol_three, COUNT(CASE WHEN status_id = pol_stat(''POL_FOUR'') THEN 1 ELSE null END) AS count_pol_four FROM purchaseorder_line WHERE purchaseorder_id = my_purchaseorder_id GROUP BY purchaseorder_id ) AS foo WHERE id = purchaseorder_id; RETURN NULL; END;'; CREATE CONSTRAINT TRIGGER purchaseorder_def AFTER INSERT OR UPDATE OR DELETE ON purchaseorder_line DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE PROCEDURE tr_purchaseorder_def(); -- **************************************************************** -- ** DEFY THE LOT -- **************************************************************** -- calling spawn_pol() at least once will avoid the error --select 1 from spawn_pol(1, 9); -- this will fail when the line above is commented out begin; select 1 from spawn_pol(2, 9); update purchaseorder_line set amount = amount where id = currval('purchaseorder_line_seq'); commit;