PGSQL: Function argument not being passed correctly - postgresql

I am attempting to search the "message" column of a table for any instance of a tag mentioned (like " #testing1 " for example).
When I manually enter the query it works but I think that I'm not understanding how to concat the tag properly:
This works:
select amount from donations where message like #testing1
But this does not:
CREATE OR REPLACE FUNCTION sum_tag(
tag TEXT)
RETURNS VOID AS $$
BEGIN
select amount from donations where message like CONCAT ('%#', tag, '%');
END; $$
LANGUAGE plpgsql;
The above is what is in the begin and end statements inside of the function sum_tag(tag text).
My intent is that I am trying to capture the tag whether it is in the message as "#testing1 this is a message" or "This is a message #testing1" and then use that as an identifier to sum up all of the amount column values for the associated rows.
Update 1
I have attempted to use the suggestion as noted in the comments but It did not seem to work. The updated function was entered as:
CREATE OR REPLACE FUNCTION sum_tag(
tag TEXT)
RETURNS VOID AS $$
BEGIN
EXECUTE format('select amount from donations where message like %s', tag);
END; $$
LANGUAGE plpgsql;
This did not work either. If I am understanding, the entire statement needs to be in single quotes and %s is the correct way to call the argument as it is a text string. I appreciate your explanation and places to look.

Related

Can you change an IN parameter inside of a plpgsql function?

Can you change a parameter that is set in the function header to something else with inside the function itself? The way I am reading this article it would seem like you can't postgres tutorial link
I am going to answer this one myself.
The short answer is Yes. You can change the default IN parameters in a plpgsql function.
Please see the following example:
--CREATE SCHEMA it
CREATE FUNCTION it.foo (_animal TEXT ) RETURNS TEXT LANGUAGE plpgsql AS
$$
BEGIN
IF _animal = 'cow' THEN
_animal = 'steak';
END IF;
RETURN _animal;
END;
$$;
SELECT it.foo('cow');

Postgres: Returning Results or Error from Stored Functions

I am struggling to figure out how to best handle the return of results or errors to my application from Postgres stored functions.
Consider the following contrived psudeocode example:
app.get_resource(_username text)
RETURNS <???>
BEGIN
IF ([ ..user exists.. ] = FALSE) THEN
RETURN 'ERR_USER_NOT_FOUND';
END IF;
IF ([ ..user has permission.. ] = FALSE) THEN
RETURN 'ERR_NO_PERMISSION';
END IF;
-- Return the full user object.
RETURN QUERY( SELECT 1
FROM app.resources
WHERE app.resources.owner = _username);
END
The function can fail with a specific error or succeed and return 0 or more resources.
At first I tried creating a custom type to always use as a standard return type in eachh function:
CREATE TYPE app.appresult AS (
success boolean,
error text,
result anyelement
);
Postgres does not allow this however:
[42P16] ERROR: column "result" has pseudo-type anyelement
I then discovered OUT parameters and attempted the following uses:
CREATE OR REPLACE FUNCTION app.get_resource(
IN _username text,
OUT _result app.appresult -- Custom type
-- {success bool, error text}
)
RETURNS SETOF record
AS
$$
BEGIN
IF 1 = 1 THEN -- just a test
_result.success = false;
_result.error = 'ERROR_ERROR';
RETURN NULL;
END IF;
RETURN QUERY(SELECT * FROM app.resources);
END;
$$
LANGUAGE 'plpgsql' VOLATILE;
Postgres doesn't like this either:
[42P13] ERROR: function result type must be app.appresult because of OUT parameters
Also tried a similar function but reversed: Returning a custom app.appresult object and setting the OUT param to "SETOF RECORD". This was also not allowed.
Lastly i looked into Postgres exception handling using
RAISE EXCEPTION 'ERR_MY_ERROR';
So in the example function, i'd just raise this error and return.
This resulted in the driver sending back the error as:
"ERROR: ERR_MY_ERROR\nCONTEXT: PL/pgSQL function app.test(text) line 6 at RAISE\n(P0001)"
This is easy enough to parse but doing things this way feels wrong.
What is the best way to solve this problem?
Is it possible to have a custom AppResult object that i could return?
Something like:
{ success bool, error text, result <whatever type> }
//Edit 1 //
I think I'm leaning more towards #Laurenz Albe solution.
My main goal is simple: Call a stored procedure which can return either an error or some data.
Using RAISE seems to accomplish this and the C++ driver allows easy checking for an error condition returned from a query.
if ([error code returned from the query] == 90100)
{
// 1. Parse out my overly verbose error from the raw driver
// error string.
// 2. Handle the error.
}
I'm also wondering about using custom SQLSTATE codes instead of parsing the driver string.
Throwing '__404' might mean that during the course of my SPs execution, it could not continue because some record needed was not found.
When calling the sql function from my app, i have a general idea of what it failing with a '__404' would mean and how to handle it. This avoids the additional step of parsing driver error string.
I can also see the potential of this being a bad idea.
Bedtime reading:
https://www.postgresql.org/docs/current/static/errcodes-appendix.html
This is slightly opinion based, but I think that throwing an error is the best and most elegant solution. That is what errors are for!
To distinguish various error messages, you could use SQLSTATEs that start with 6, 8 or 9 (these are not used), then you don't have to depend on the wording of the error message.
You can raise such an error with
RAISE EXCEPTION SQLSTATE '90001' USING MESSAGE = 'my own error';
We do something similar to what you're trying to do, but we use TEXT rather than ANYELEMENT, because (almost?) any type can be cast to TEXT and back. So our type looks something like:
(errors our_error_type[], result TEXT)
The function which returns this stores errors in the errors array (it's just some custom error type), and can store the result (cast to text) in the result field.
The calling function knows what type it expects, so it can first check the errors array to see if any errors were returned, and if not it can cast the result value to the expected return type.
As a general observation, I think exceptions are more elegant (possibly because I come from a c# background). The only problem is in plpgsql exception handling is (relatively) slow, so it depends on the context - if you're running something many times in a loop, I would prefer a solution that doesn't use exception handling; if it's a single call, and/or especially when you want it to abort, I prefer raising an exception. In practice we use both at various points throughout our call stacks.
And as Laurenz Albe pointed out, you're not meant to "parse" exceptions, so much as raise an exception with specific values in specific fields, which the function that catches the exception can then extract and act on directly.
As an example:
Setup:
CREATE TABLE my_table (id INTEGER, txt TEXT);
INSERT INTO my_table VALUES (1,'blah');
CREATE TYPE my_type AS (result TEXT);
CREATE OR REPLACE FUNCTION my_func()
RETURNS my_type AS
$BODY$
DECLARE
m my_type;
BEGIN
SELECT my_table::TEXT
INTO m.result
FROM my_table;
RETURN m;
END
$BODY$
LANGUAGE plpgsql STABLE;
Run:
SELECT (m.result::my_table).*
FROM my_func() AS m
Result:
| id | txt |
-------------
| 1 | blah |

Function argument not interpreted inside body

I am trying to create a PL/pgSQL function in PostgreSQL 9.3.6, but there is weird behavior when using the passed argument inside function body. Here is the 'very simple' function:
CREATE OR REPLACE FUNCTION myschema.test (myarg text) RETURNS text AS $$
DECLARE
entity text;
buffer text;
BEGIN
CREATE ROLE myarg;
RETURN myarg;
END;
$$ LANGUAGE plpgsql;
So, if for instance myarg equals 'test':
A role named 'myarg' is created (WRONG)
'test' is returned (CORRECT)
I searched for hours why this could be and no clue... security parameter? Why is myarg not interpreted for creating roles?
Testing with phpPgAdmin through sql files if this has any impact.
You should use:
EXECUTE FORMAT('CREATE ROLE %I', myarg);
Here you can find an explanation (especially read Craig's answer).
As Erwin stated (thanks), %I is safer than %s. Anyway, myarg should be verified before the function call. Try for example
SELECT myschema.test('something; stupid; here;')

Display Comments while Running Script

I've seen the answer to this question in a couple posts. However when I the below which was an answer coming from another post, I get an error. My objective is to simply write comments to the screen as I execute DML commands in a script. However I have not found a simple way to do this.
CREATE OR REPLACE FUNCTION raise_exception(text)
RETURNS void AS $$
BEGIN
RAISE EXCEPTION '%', $1;
END;
$$ LANGUAGE plpgsql;
Called like this: select * from sp_send_msg('go for it');
I would like to be able to do something like this:
SELECT send_comment('Writing widgets to temporary table');
SELECT * INTO t_widgets FROM widgets;
SELECT send_comment('Writing temporary table into new widget table');
INSERT INTO new_widgets
SELECT * FROM t_widgets;
Thanks in advance for any helpful guidance on this. I'm running PostgreSQL 8.4.7.
You should raise notice instead of raise exception. The former will show a message and the latter is treated as an error and will abort your transaction.

How can use custom_variable_class

i am create custom_variable_class= myapp in postgresql.conf. And set the value is inside of function like
CREATE OR REPLACE FUNCTION fn_purchase(xmode text, xuserno integer)
RETURNS text AS
set myapp.user_no=xuserno
..........
..........
END;
Now i am using myapp.user in my trigger
CREATE OR REPLACE FUNCTION public.delete_history()
RETURNS trigger AS
$BODY$
DECLARE userno text;
BEGIN
SELECT current_setting('myapp.user_no') into userno;
END;
$BODY$
If set userno is integer then it show error message.
invalid input syntax for integer: "xuserno".
and
SELECT current_setting('myapp.user_no') is show xuserno not xuserno value. That means xuserno=5 it show xuserno not 5. I am doing any thing wrong?
Your problem is a very subtle one:
According to the docs instead of:
custom_variable_class= myapp
You should have
custom_variable_classes='myapp'
Note that custom_variable_classes is intended to be used to set multiple classes for multiple addons.
From there, with the comments above, this should work.