Postgresql regex in tsvector update - sql

I have the following update trigger for a tsvector column
CREATE TRIGGER tsvector_user_update
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE
tsvector_update_trigger(user_tsv, 'pg_catalog.english', firstname, surname, email, card_id);
This works fine, however my card_id column (text) contains a pre-amble that the user is not aware of (it is added after the card is scanned), so I would like to strip out the pre-amble when the tsvector value is generated, I have tried the trigger function as a start
CREATE FUNCTION user_change_trigger() RETURNS trigger AS $$
BEGIN
NEW.user_tsv = setweight(to_tsvector('pg_catalog.english', coalesce(NEW.firstname,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.surname,'')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(REGEXP_REPLACE(NEW.card_id, '^\d+PRE', ''),'')), 'B') ||
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.email,'')), 'C');
return new;
END
$$ LANGUAGE plpgsql;
CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE
ON users FOR EACH ROW EXECUTE PROCEDURE user_change_trigger();
Which executes, but I get the following:
WARNING: nonstandard use of escape in a string literal
And no updated tsvector
The pre-amble is an integer followed by 'PRE'.
(PostgreSQL 9.0)

Basic trigger design
The problem is of principal nature. In PostgreSQL you create a trigger function that does the work. I don't see your trigger function in the question.
Then you create a trigger which makes use of this function. You can only pass constants to a trigger function. Consider this quote from the manual about CREATE TRIGGER
function_name
A user-supplied function that is declared as taking no arguments and
returning type trigger, which is executed when the trigger fires.
arguments
An optional comma-separated list of arguments to be provided to the
function when the trigger is executed. The arguments are literal
string constants. Simple names and numeric constants can be written
here, too, but they will all be converted to strings. Please check the
description of the implementation language of the trigger function to
find out how these arguments can be accessed within the function; it
might be different from normal function arguments.
Bold emphasis mine.
Use NEW to access the column values inside the trigger function. You don't need to pass them as arguments. Get a grip on the basic concept first. Start here.
regexp_replace()
Use:
regexp_replace(card_id, '^\d+PRE', '')
.. since the leading characters are supposed to be digits only (and at least one of them).
Proper trigger & function
The following test case works for me on PostgreSQL 9.1.6. Your version looks basically good to me, I only made minor changes. But keep reading ...
Create test environment (will be rolled back at the end):
BEGIN;
CREATE SCHEMA test;
SET search_path = test;
CREATE TABLE users (
users_id serial primary key
,firstname text
,surname text
,card_id text
,email text
,user_tsv tsvector
);
Trigger function:
CREATE FUNCTION user_change_trigger()
RETURNS trigger AS
$func$
BEGIN
NEW.user_tsv :=
setweight(to_tsvector('pg_catalog.english', coalesce(NEW.firstname,'')), 'A')
|| setweight(to_tsvector('pg_catalog.english', coalesce(NEW.surname,'')), 'A')
|| setweight(to_tsvector('pg_catalog.english', coalesce(regexp_replace(NEW.card_id, '^\d+PRE', ''),'')), 'B')
|| setweight(to_tsvector('pg_catalog.english', coalesce(NEW.email,'')), 'C');
RETURN NEW;
END
$func$ LANGUAGE plpgsql;
The assignment operator of plpgsql is := - unlike SQL where = is used.
Trigger:
CREATE TRIGGER tsvectorupdate
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE PROCEDURE user_change_trigger();
Tests:
INSERT INTO users (firstname, surname, card_id, email)
VALUES ('Erwin', 'Brandstetter', '123PRE456', 'foo#dummy.org')
RETURNING *;
-- looks good!
UPDATE users SET firstname = 'Walter' WHERE TRUE
RETURNING *;
-- looks good, too!
Clean up:
ROLLBACK;
standard_conforming_strings
Explore your setting of standard_conforming_strings. The WARNING suggests that you don't have this setting on, which would require that you double the backslash in:
'^\\d+PRE'

Related

How to pass arguments from a function to the creation of a TRIGGER?

I am trying to make a TRIGGER that responds on an update at a table (appointments) which then calls a procedure (proc1()). The procedure needs to get arguments in order to insert a new row -based on those arguments- on a different table (medical_folder). Procedures can't have arguments, but after a bit of search I found that you can use a method like the following to kinda force your way though:
Passing arguments to a trigger function
With the above as my base I made the following UDF:
CREATE OR REPLACE FUNCTION AppointmentUpdate(docAMKA bigint, patAMKA bigint, dateNtime timestamp, conclusion varchar(500),cure2 varchar(500), drug_id integer)
RETURNS void AS $$
DECLARE
patAMKAv2 text;
drug_idv3 text;
BEGIN
patAMKAv2 := cast(AppointmentUpdate.patAMKA as text);
drug_idv3 := cast(AppointmentUpdate.drug_id as text);
DROP TRIGGER IF EXISTS tr1 on appointments;
CREATE TRIGGER tr1 BEFORE UPDATE ON appointments
EXECUTE PROCEDURE proc1(patAMKAv2,cure2,drug_idv3);
UPDATE appointments
SET diagnosis = conclusion
WHERE patientamka = patAMKA
AND doctoramka = docAMKA
AND t = dateNtime;
END;
$$ LANGUAGE plpgsql;
My procedure is as follows:
CREATE OR REPLACE FUNCTION proc1()
RETURNS trigger AS $$
declare
newid integer;
BEGIN
newid =((select max(medical_folder.id) from medical_folder)+1);
INSERT INTO medical_folder AS Medf(id,patient,cure,drug_id)
VALUES(newid,cast(TG_ARGV[0] as bigint),TG_ARGV[1],cast(TG_ARGV[2] as integer));
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
If I run it as is I am getting an error in proc1() here cast(TG_ARGV[0] as bigint) and it seems that in the UDF instead of sending the value of the arguments it sends the arguments themselves(if for example i do this EXECUTE PROCEDURE proc1(324,cure2,234); INSTEAD OF THIS EXECUTE PROCEDURE proc1(patAMKAv2,cure2,drug_idv3); ). Is there any way that you can force it to get the values instead?
P.S.:I now that this can be done a lot easier without the use of a TRIGGER and just make a UDF that does everything itself but unfortunately I have to do it using a TRIGGER.
P.S.2: I tried using function_name.variable_name instead of just var_name also tried using $1, $2, $3, ... , $n.
From the CREATE TRIGGER docs:
arguments
An optional comma-separated list of arguments to be provided to the function when the trigger is executed. The arguments are literal string constants. Simple names and numeric constants can be written here, too, but they will all be converted to strings.
http://rextester.com/OCA59277
You might be able to achieve what you are trying with dynamic SQL though (see EXECUTE). But I believe that you overcomplicate things. What you want to do is simply to get rows or IDs which participated in an UPDATE statement. PostgreSQL's DML statements (INSERT, UPDATE & DELETE) has a RETURNING clause just for that. Also, you can actually write more DML (sub-)statements within a single statement with writeable CTEs. Something like this should suffice:
WITH upd AS (
UPDATE appointments
SET diagnosis = conclusion
WHERE patientamka = patAMKA
AND doctoramka = docAMKA
AND t = dateNtime
RETURNING *
)
INSERT INTO medical_folder(patient, cure, drug_id)
SELECT patAMKAv2, cure2, drug_idv3
FROM upd;
Note: while writing this I realized that you actually don't use any of the fields from the UPDATE, but using FROM upd will ensure that as much rows will be inserted into medical_folder just as much appointments got updated. Which is what your original trigger-based logic did.

PostgreSQL Update trigger

I have a table:
CREATE TABLE annotations
(
gid serial NOT NULL,
annotation character varying(250),
the_geom geometry,
"rotationAngle" character varying(3) DEFAULT 0,
CONSTRAINT annotations_pkey PRIMARY KEY (gid),
CONSTRAINT enforce_dims_the_geom CHECK (st_ndims(the_geom) = 2),
CONSTRAINT enforce_srid_the_geom CHECK (st_srid(the_geom) = 4326)
)
And trigger:
CREATE TRIGGER set_angle
AFTER INSERT OR UPDATE
ON annotations
FOR EACH ROW
EXECUTE PROCEDURE setangle();
And function:
CREATE OR REPLACE FUNCTION setAngle() RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
UPDATE annotations SET "rotationAngle" = degrees( ST_Azimuth( ST_StartPoint(NEW.the_geom), ST_EndPoint(NEW.the_geom) ) )-90 WHERE gid = NEW.gid;
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
And when new row inserted in table or row edited i want to field rotationAngle setted with function result.
But when i inserting a new row in table function not work. I mean thath rotationAngle value not changed.
What can be wrong?
You are triggering an endless loop. Simplify the trigger function:
CREATE OR REPLACE FUNCTION set_angle()
RETURNS trigger
LANGUAGE plpgsql AS
$func$
BEGIN
NEW."rotationAngle" := degrees(
ST_Azimuth(
ST_StartPoint(NEW.the_geom)
, ST_EndPoint(NEW.the_geom)
)
) - 90;
RETURN NEW;
END
$func$;
Assign to NEW directly. No WHERE in this case.
You must double-quote illegal column names. Better not to use such names to begin with.
Recent related answer.
Code for insert & upgrade is the same. I folded into one code path.
Use a BEFORE trigger. This way you can edit columns of the triggering row directly before they are saved:
CREATE TRIGGER set_angle
BEFORE INSERT OR UPDATE ON annotations
FOR EACH ROW EXECUTE PROCEDURE set_angle();
However
If you are just trying to persist a functionally dependent value in the table (and there are no other considerations): Don't. Use a view or a generated column instead:
Store common query as column?
Then you don't need any of this.
There are multiple things wrong here.
1) When you insert a row 'A' the function setAngle() is called. But in the function you are calling another update within the function which will trigger the function again, and again, and so on...To fix this don't issue a update! Just update the NEW records value independently and return it.

Use DEFAULT value if empty string

How can I tell Postgres that a column with an empty string should use the DEFAULT value?
Using CHECK doesn't seem to work here.
A check constraint will only prevent putting such a value into the column. It will not magically replace the supplied value with the default value.
In order to silently replace an empty string (or null) with the value 'active' you will need a trigger:
create or replace function check_default()
returns trigger
as
$body$
begin
if (new.status is null or new.status = '') then
new.status = 'active';
end if;
return new;
end;
$body$
language plpgsql;
The above trigger would catch stuff like this:
insert into foo (id, active)
values (42,'');
update foo
set active = ''
where id = 42;
You could even extend that logic to also replace whitespace only values (' ') with the desired value.
Although it is possible to retrieve the default value dynamically in the trigger (to avoid having the same constant in two places) I would not do that for performance reasons.

PL/pgSQL function + client-side lo_import

I have problem with importing documents to postgres db. I have plpgsql function, simplier version could look like that:
create function add_file(flag integer, sth varchar) returns void as
begin
if flag = 1 then
insert into tab_one values (my_file_oid, sth);
else
insert into tab_two values (my_file_oid, sth);
end if;
end;
And psql command:
\lo_import('path/to/file');
Both code in one file. I cant put lo_import() to insert statement, becouse I need client-site lo_import. There is variable LASTOID, but it is not avaible in add_file function. And it wouldnt be updating on every call add_file().
So, how can I put oid to database with, in our example, 'flag' and 'sth' by insert statement and everything in function with arguments? File is in client computer.
psql's \lo_import returns the OID resulting from the import. You need to hand that in as parameter to the function, which could look like this:
CREATE FUNCTION add_file(_flag integer, _sth varchar, _oid oid)
RETURNS void LANGUAGE plpgsql AS
BEGIN
IF _flag = 1 THEN
INSERT INTO tab_one(file_oid, sth) VALUES (_oid, _sth);
ELSE
INSERT INTO tab_two(file_oid, sth) VALUES (_oid, _sth);
END IF;
END;
As an aside: always add a column list to your table with an INSERT command (except for ad-hoc calls, maybe).
From within a plpgsql function you can make use to the also provided server side functions. Could look like this:
INSERT INTO tab_one(file_oid, sth) VALUES (lo_import('/etc/motd'), _sth);
Note that this operates within the file system of the database server with the privileges of the owner (usually system user postgres). Therefore, use is restricted to superusers.

Postgresql trigger function with parameters

I want to create a trigger on a table called takes in postgresql to update a value in another table called student
I'm trying to do it in the following way. But I'm getting an error that there is syntax error near "OLD". I don't understand whats wrong with this. This is my code:
CREATE OR REPLACE FUNCTION upd8_cred_func
(id1 VARCHAR, gr1 VARCHAR,id2 VARCHAR, gr2 VARCHAR)
RETURNS void AS $$
BEGIN
IF (id1=id2 and gr1 is null and gr2 is not null) THEN
update student set tot_cred = tot_cred + 6 where id = id1;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER upd8_cred
AFTER UPDATE ON takes
FOR EACH ROW
EXECUTE PROCEDURE upd8_cred_func(OLD.id,OLD.grade,NEW.id,NEW.grade);
You do not need to pass the NEW and OLD as parameters to the trigger function. They are automagically available there:
http://www.postgresql.org/docs/9.1/interactive/trigger-definition.html :
The trigger function must be declared as a function taking no arguments and returning type trigger. (The trigger function receives its input through a specially-passed TriggerData structure, not in the form of ordinary function arguments.)
About the records passed to the trigger procedure, please see http://www.postgresql.org/docs/9.1/interactive/plpgsql-trigger.html :
When a PL/pgSQL function is called as a trigger, several special variables are created automatically in the top-level block. They are: [...] NEW, [...] OLD [...]
As SeldomNeedy pointed in the comment below, you can still pass and use parameters to the trigger function. You declare the function as taking no parameters, but when defining the trigger (by CREATE TRIGGER), you may add some.
They will be available for the trigger as TG_NARG (the number of such parameters), and TG_ARGV[] (an array of text values).
As Greg stated, trigger functions can take arguments, but the functions themselves cannot have declared parameters. Here's a simple example in plpgsql:
CREATE TABLE my_table ( ID SERIAL PRIMARY KEY ); -- onelined for compactness
CREATE OR REPLACE FUNCTION raise_a_notice() RETURNS TRIGGER AS
$$
DECLARE
arg TEXT;
BEGIN
FOREACH arg IN ARRAY TG_ARGV LOOP
RAISE NOTICE 'Why would you pass in ''%''?',arg;
END LOOP;
RETURN NEW; -- in plpgsql you must return OLD, NEW, or another record of table's type
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_inserts_without_notices BEFORE INSERT ON my_table
FOR EACH ROW EXECUTE PROCEDURE raise_a_notice('spoiled fish','stunned parrots');
INSERT INTO my_table DEFAULT VALUES;
-- the above kicks out the following:
--
-- NOTICE: Why would you pass in 'spoiled fish'?
-- NOTICE: Why would you pass in 'stunned parrots'?
--
There are a few other goodies such as TG_NARGS (to know how many args you got without looping through them) discussed in the docs. There's also information there about how to get the name of the triggering table in case you have mostly-but-not-quite-shared logic for one trigger-function that spans a number of tables.
The trigger function can have parameters, but, you can't have those parameters passed like a normal function (e.g. arguments in the function definition). You can get the same result... In python you get access to the OLD and NEW data as the answer above describes. For example, I can use TD['new']['column_name'] in python to reference the new data for column_name. You also have access to the special variable TD['args']. So, if you like:
create function te() returns trigger language plpython2u as $function$
plpy.log("argument passed 1:%s 2:%s" %(TD['args'][0], TD['args'][1], ))
$function$
create constraint trigger ta after update of ttable
for each for execute procedure te('myarg1','myarg2');
Granted, these arguments are static, but, they are useful when calling a common trigger function from multiple trigger declarations. I am pretty sure that the same variables are available for other stored procedure languages. (sorry if the code doesn't work verbatim, but, I do practice this technique, so I know you can pass arguments!).