Hi Tom,
> Because the trigger function is plpgsql, this could happen only the
> first time the trigger is fired in a particular session (unless you are
> using EXECUTE to invoke the update command?). If the problem is related
> to the planner constant-folding environment, then the first-time-only
> limitation would help make it hard to reproduce.
It looks like your suspicion was right.... I've isolated one particulare case,
simply run the script attached. If you remove the comment before the extra
call of spawn_pol() at the bottom of the script, the error is avoided.
Hopefully, solving this will also solve the other cases of mem-alloc error
I've been seeing ;) I'm not using spawn_pol/sol() during the earlier
mentioned conversion, but my hope is that the error generated during
conversion will turn out to be related and caused by the constant-folding of
some other function. But I'll know soon enough if it isn't ;)
Looking forward to your assesment.
--
Best,
Frank.
-- ****************************************************************
-- ** 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;
---------------------------(end of broadcast)---------------------------
TIP 8: explain analyze is your friend