PL/pgSQL function + client-side lo_import - sql

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.

Related

PL/pgSQL function to return the output of various SELECT queries from different database

I have found this very interesting article: Refactor a PL/pgSQL function to return the output of various SELECT queries
from Erwin Brandstetter which describes how to return all columns of various tables with only one function:
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM data_of(NULL::tablename,'1=1 LIMIT 1');
This works pretty well. I need a very similar solution but for getting data from a table on a different database via dblink. That means the call NULL::tablename will fail since the table does not exists on the database where the call is made. I wonder how to make this work. Any try to connect inside of the function via dblink to a different database failed to get the result of NULL::tablename. It seems the polymorph function needs a polymorph parameter which creates the return type of the function implicit.
I would appreciate very much if anybody could help me.
Thanks a lot
Kind regards
Brian
it seems this request is more difficult to explain than I thought it is. Here is a second try with a test setup:
Database 1
First we create a test table with some data on database 1:
CREATE TABLE db1_test
(
id integer NOT NULL,
txt text
)
WITH (
OIDS=TRUE
);
INSERT INTO db1_test (id, txt) VALUES(1,'one');
INSERT INTO db1_test (id, txt) VALUES(2,'two');
INSERT INTO db1_test (id, txt) VALUES(3,'three');
Now we create the polymorph function on database 1:
-- create a polymorph function with a polymorph parameter "_table_name" on database 1
-- the return type is set implicit by calling the function "data_of" with the parameter "NULL::[tablename]" and a where part
CREATE OR REPLACE FUNCTION data_of(_table_name anyelement, _where_part text)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE
'SELECT * FROM ' || pg_typeof(_table_name)::text || ' WHERE ' || _where_part;
END
$func$ LANGUAGE plpgsql;
Now we make test call if everything works as aspected on database 1
SELECT * FROM data_of(NULL::db1_test, 'id=2');
It works. Please notice I do NOT specify any columns of the table db1_test. Now we switch over to database 2.
Database 2
Here I need to make exactly the same call to data_of from database 1 as before and although WITHOUT knowing the columns of the selected table at call time. Unfortunatly this is not gonna work, the only call which works is something like that:
SELECT
*
FROM dblink('dbname=[database1] port=[port] user=[user] password=[password]'::text, 'SELECT * FROM data_of(NULL::db1_test, \'id=2\')'::text)
t1(id integer, txt text);
Conclusion
This call works, but as you can see, I need to specify at least once how all the columns look like from the table I want to select. I am looking for any way to bypass this and make it possible to make a call WITHOUT knowing all of the columns from the table on database 1.
Final goal
My final goal is to create a function in database 2 which looks like
SELECT * from data_of_dblink('table_name','where_part')
and which calls internaly data_of() on database1 to make it possible to select a table on a different database with a where part as parameter. It should work like a static view but with the possiblity to pass a where part as parameter.
I am extremly open for suggestions.
Thanks a lot
Brian

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.

ORACLE PLSQL Function/Trigger

I'm trying to create a FUNCTION/TRIGGER that can update the status_comunal_waste in table supply without the need of writing a query.
EDIT:
TRAFFIC (date),(input_quant),(output_quant),(status)
SUPPLY (input_quant), (output_quant),(status_comunal_waste)
After I import the DATE, INPUT, OUTPUT, STATUS data into the table TRAFFIC,
with the trigger AIR the data is also imported into the table SUPPLY, with the execption of columns USER_CHANGES, DATE_CHANGES, USER_DELETE, DATE_DELETE as to mimic the changes the "user" has made if the "user" decides to change or add the data.
So far I've tried something like this
create or replace TRIGGER supply_after_insert
after INSERT
ON traffic
FOR EACH ROW
DECLARE
Status_comunal_waste VARCHAR2(20);
BEGIN
SELECT SUM(input_quant) - SUM(output_quant)
into Status_comunal_waste
FROM traffic
WHERE (key_num_code =200101);
INSERT INTO supply
(Status_comunal_waste) VALUES (Status_comunal_waste);
END;
EDIT:
Meanwhile I switched from creating a trigger to function, and this is written so far:
create or replace
FUNCTION stat_comunal_waste (key_num_code number)
return number
is
status_comunal_waste NUMBER;
BEGIN
if (key_num_code = 200101) THEN
SELECT SUM (input_quant) - SUM (output_quant)
into status_comunal_waste
FROM traffic
WHERE key_num_code =200101;
end if;
return status_comunal_waste;
END;
The function compiled with no errors but now I get errors with the PACKAGE
CREATE OR REPLACE PACKAGE Paket
IS
function stat_comunal_waste (key_num_code number)
return status_comunal_waste;
end Paket;
/
Error(3,1): PL/SQL: Declaration ignored
Error(4,8): PLS-00488: 'status_comunal_waste' must be a type
This query might be wrong.
SELECT SUM(input_quant) - SUM(output_quant) into Status_comunal_waste
FROM traffic WHERE key_num_code =200101);
Make sure you are getting the correct values. Try to replace the above query with code below and give it a try
select SUM(input_quant) into input_q FROM traffic WHERE key_num_code =200101;
select SUM(output_quant) into output_q FROM traffic WHERE key_num_code =200101;
diff := input_q - output_q;
Final insert will be like:
INSERT INTO status (Status_comunal_waste) VALUES (diff);

Postgresql regex in tsvector update

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'

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!).