Implement advisory lock inside stored procedure - postgresql

I have a stored procedure shown below which is running every 15 minutes trigged from postgres cron. Usually it happens that this procedure needs more time to finish. In such situation when next schedule is achieved and procedure is called again I would like procedure to be still called but wait till previous is finished. How can I achieve that without losing any new procedure call? I've heard that this can be achieved by using advisory lock nevertheless i am not sure how should I prepare my procedure to implement such feature. Can anyone show how exactly could i prepare my procedure?
My stored procedure:
CREATE OR REPLACE PROCEDURE public.test_procedure(p1 integer,
INOUT p2 character varying)
LANGUAGE plpgsql
AS $procedure$
DECLARE
loop_var int;
BEGIN
IF p1 is null OR p2 is null THEN
RAISE EXCEPTION 'input cannot be null';
END IF;
DROP TABLE if exists test_table;
CREATE TEMP TABLE test_table(a int, b varchar);
FOR loop_var IN 1..p1 LOOP
insert into test_table values (loop_var, p2);
p2 := p2 || '+' || p2;
END LOOP;
END;
$procedure$
Procedure after modification:
CREATE OR REPLACE PROCEDURE public.test_procedure(p1 integer,
INOUT p2 character varying)
LANGUAGE plpgsql
AS $procedure$
DECLARE
loop_var int;
BEGIN
SELECT pg_advisory_lock(4711);
IF p1 is null OR p2 is null THEN
RAISE EXCEPTION 'input cannot be null';
END IF;
DROP TABLE if exists test_table;
CREATE TEMP TABLE test_table(a int, b varchar);
FOR loop_var IN 1..p1 LOOP
insert into test_table values (loop_var, p2);
p2 := p2 || '+' || p2;
END LOOP;
SELECT pg_advisory_unlock(4711);
END;
$procedure$

Per the documentation:
At the beginning of the procedure, take an exclusive advisory lock:
PERFORM pg_advisory_lock(4711);
Immediately before the end, release the lock:
PERFORM pg_advisory_unlock(4711);
The number 4711 is a random number, all that counts is that you don't use the same advisory lock number anywhere else.
Then the second concurrent execution of the function will block when it tries to take the advisory lock and will only be allowed to continue when the lock is released.

Related

Postgres constraint for a subset of rows

I have a Postgres table containing tasks (tasks). A task can link to many entities using a link table (links). Tasks can be one of many types.
A subset of tasks, denoted by their types (let's call it S) can only link to one entity. That is, in link, there can be exactly one record with that task's ID/primary key.
Is there a way to encode that into Postgres constraints so that's managed automatically?
I found a solution to this using triggers:
CREATE OR REPLACE FUNCTION unique_link() RETURNS trigger AS $unique_link$
DECLARE
t_type text;
link_ct int;
BEGIN
SELECT task_type INTO STRICT t_type FROM tasks WHERE id = NEW.task_id;
IF t_type = 'S' THEN
SELECT COUNT(*) INTO STRICT link_ct FROM links
WHERE task_id = NEW.task_id
IF link_ct > 0 THEN
RAISE EXCEPTION '% of type % already has a link associated', NEW.task_id, t_type;
END IF;
RETURN NEW;
END IF;
RETURN NEW;
END;
$unique_link$ LANGUAGE plpgsql;
CREATE TRIGGER unique_link BEFORE INSERT OR UPDATE ON links
FOR EACH ROW EXECUTE PROCEDURE unique_link();

Commit and rollback statement (in a child stored procedure) affect main stored procedure in Postregsql?

I have 2 stored procedures in Postgresql: sp_main, sp_child.
sp_main calls sp_child in the procedure. The problem is that whenever sp_child input1 = -1 then it rollbacks sp_main first insert statement.
How can I use savepoint statement for this situation?
Question summary:
call1() --> call2() (call2 will have commit/rollback but will not affect call1)
Code:
CREATE OR REPLACE PROCEDURE sp_main(INOUT main_result integer)
LANGUAGE plpgsql
AS $$
begin
declare resultc integer
resultc=0;
---
---first insert
insert into table1
values(-1,'a'); --input1=-1
---
call sp_child(1,resultc); --call child sp.
main_result=resultc;
---
---last insert
insert into table2
values(-7,'a');
commit;
END ;
$$
;
CREATE OR REPLACE PROCEDURE sp_child(input1 integer DEFAULT 0,INOUT v_result integer DEFAULT 5)
LANGUAGE plpgsql
AS $$
begin
v_result=1;
---
---query_child
insert into table2
values(-10,'a');
---
if input1=-1 then
Rollback;
end if;
END ;
$$
;
call sp_main(1);
A savepoint won't help you (in PL/pgSQL, that in implemented by a BEGIN ... EXCEPTION ... END block). A savepoint starts a subtransaction, but if you ROLLBACK, the whole transaction including all of its subtransactions will be aborted.
There is no way to keep a transaction open in sp_main if sp_child issues a ROLLBACK.
You will have to redesign your transaction handling. The way it is right now doesn't make sense anyway: Any INSERT that sp_child performs is rolled back.

How to create thousands or millions of tables at a time in PostgreSQL?

Could you please advise me SQL-based or database PostgreSQL specific command(s) or flow to create thousands or even millions of similar (same) 2-column-based empty tables in PostgreSQL RDBMS? Maybe based on basic empty table as token 'fw'? 'fw_'+(increment)
The solution has to be as fast as possible. Maybe some trick(?) Thank you.
You can use dynamic SQL in a loop.
do
$$
declare
l_counter integer;
l_name text;
l_sql text;
begin
for l_counter in 1..10000 loop
l_name := 'fw_'||to_char(l_counter, 'FM00000000');
l_sql := format('create table %I (col1 integer, col2 integer)', l_name);
execute l_sql;
end loop;
end;
$$;
But this sounds like a really bad idea.
Creating 10000 tables or orders on magnitude more is not going to be blindingly fast - it's just not going to happen. But the following MAY be a little quicker.
do $$
declare stmt record;
begin
for stmt in
with s as (select (generate_series (1,100)) n )
select 'create table tbl_' || to_char(n, 'FM00000000') || ' (i1 integer, i2 integer); ' as t from s
loop
execute stmt.t ;
end loop;
end ; $$;
As suggested let us know what issue your trying to resolve. Perhaps then someone will see a variable alternation. I would ask: After you create these tables how will you use them and how will you know which one to use?

stored procedure avoid deadlock for concurrent statements

I have the following stored procedure
CREATE OR REPLACE FUNCTION testFunction(iRowID1 integer, iRowID2 integer) RETURNS void AS $$
BEGIN
UPDATE Table1 SET Value1=Value1+1 WHERE rowID=iRowID1;
UPDATE Table1 SET Value1=Value1-1 WHERE rowID=iRowID2;
END;
$$ LANGUAGE plpgsql;
If I run the following two commands concurrently
SELECT testFunction(1,2);
SELECT testFunction(2,1);
I get a deadlock detected error for one of the commands. Is there some way to avoid this deadlock?
I can't test this right now as I don't have access to a PostgreSQL database at the moment, but in theory it should work, as deadlocks can always be avoided if you lock things in the same order and never escalate a lock level (upgrade a read lock to a write lock, for example).
Do the updates in a specific order:
CREATE OR REPLACE FUNCTION testFunction(iRowID1 integer, iRowID2 integer) RETURNS void AS $$
BEGIN
IF iRowID1 < iRowID2 THEN
UPDATE Table1 SET Value1=Value1+1 WHERE rowID=iRowID1;
UPDATE Table1 SET Value1=Value1-1 WHERE rowID=iRowID2;
ELSE
UPDATE Table1 SET Value1=Value1-1 WHERE rowID=iRowID2;
UPDATE Table1 SET Value1=Value1+1 WHERE rowID=iRowID1;
END IF
END;
$$ LANGUAGE plpgsql;
That will always update the rows in numerically-ascending order, thus in your example row 1 will always be updated before row 2, and the second invocation can't start its update until the first invocation is done.

PostgreSQL trigger and stored procedure not kicking in

My goal is to compute and persist a certain value before an update happens to a row in my table.
I created the trigger and the function, I don't get any errors but the function doesn't seem to be kicking in. Where am I going wrong?
The procedure
CREATE or REPLACE FUNCTION foo() returns trigger as
$BODY$
DECLARE
BEGIN
NEW.geomtry := st_transform(st_pointfromtext('POINT(' || NEW.af_lon || ' ' || NEW.af_lat || ')', 4326), 32643);
return NEW;
END;
$BODY$
language plpgsql;
The trigger to trigger the method before an update happens
CREATE TRIGGER foo_trigger BEFORE UPDATE ON foo_table FOR EACH ROW EXECUTE PROCEDURE foo();
You spelled "geometry" incorrectly in your stored proc. Make sure it is spelled consistently in your table and stored proc.