"function does not exist," but I really think it does - postgresql

Am I crazy or just plain dumb?
dev=# \df abuse_resolve
List of functions
-[ RECORD 1 ]-------+------------------------------------------------------------------------------------------------------------------------------------
Schema | public
Name | abuse_resolve
Result data type | record
Argument data types | INOUT __abuse_id bigint, OUT __msg character varying
Type | normal
dev=# select abuse_resolve('30'::bigint);
ERROR: function abuse_resolve(bigint) does not exist
LINE 1: select abuse_resolve('30'::bigint);
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
Here's the CREATE FUNCTION, I've omitted the meat of the code, but that should be irrelevant:
CREATE OR REPLACE FUNCTION abuse_resolve(INOUT __abuse_id bigint, OUT __msg character varying) RETURNS record AS $_$
DECLARE
__abuse_status VARCHAR;
BEGIN
...snip...
UPDATE abuse SET abuse_status = __abuse_status,
edate = now(),
closed_on = now()
WHERE abuse_id = __abuse_id;
__msg = 'SUCCESS';
END;
$_$ LANGUAGE plpgsql SECURITY DEFINER;
And just for giggles:
GRANT ALL ON FUNCTION abuse_resolve(INOUT __abuse_id, OUT __msg character varying) TO PUBLIC;
GRANT ALL ON FUNCTION abuse_resolve(INOUT __abuse_id, OUT __msg character varying) TO myuser;
That function seems like it exists. What could I be missing?
This is resolved, the answer is: I'm dumb. I had improperly defined the arguments originally, but my code was using the correct ones. There was an extra bigint that had no business being there.

Well, something is odd. I did:
steve#steve#[local] =# create function abuse_resolve(inout __abuse_id bigint,
out __msg text) returns record language plpgsql as
$$ begin __msg = 'ok'; end; $$;
CREATE FUNCTION
steve#steve#[local] =# \df abuse_resolve
List of functions
-[ RECORD 1 ]-------+----------------------------------------
Schema | so9679418
Name | abuse_resolve
Result data type | record
Argument data types | INOUT __abuse_id bigint, OUT __msg text
Type | normal
steve#steve#[local] =# select abuse_resolve('30'::bigint);
-[ RECORD 1 ]-+--------
abuse_resolve | (30,ok)
Have you had any other issues with this database? Can you copy it with dump/restore and try this on the new copy? Does explicitly qualifying the function name with the "public" schema help? Which version of PostgreSQL are you using?
update: sql function
It also worked fine for me using:
create function abuse_resolve(inout __abuse_id bigint, out __msg text)
language sql as $$ select $1, 'ok'::text $$;

If you can and if is that problem. I recommend to use
"set search_path = mainSchemaName, secondOnes"
to set correct schema where function is created or in a place where you call it directly specify the schema name
select schemaName.abuse_resolve('30'::bigint);

Try this syntax:
SELECT * FROM abuse_resolve('30'::bigint);

I had everything but no usage on the schema. Granting usage on schema fixed it.

Related

How to return result of dynamic SELECT inside a function in PostgreSQL?

A very similar question here but not quite the same as this one.
I have a function that uses IF statements to determine what type of SELECT query to return.
How can I declare what a CREATE FUNCTION statment should return when I will never know the exact columns a SELECT query within it might return? That is, I can't setup a RETURNS TABLE declaration with a list of columns because I don't know which columns might come back. All I know is that I definitely will want a table of results to be returned.
Here is my function (uncompleted, pseudo):
CREATE OR REPLACE FUNCTION functiona(_url character varying DEFAULT NULL)
RETURNS -- what type? if TABLE how do I know what columns to specify
LANGUAGE plpgsql
AS
$$
DECLARE
_urltypeid int;
BEGIN
IF _url IS NOT NULL
THEN
_urltypeid := reference.urltype(_url);
IF _urltypeid = 1
THEN
RETURN QUERY
SELECT location, auxiliary, response FROM tablea -- unique columns from one table
END IF;
IF _urltypeid = 2
THEN
RETURN QUERY
SELECT ip, location, host, authority FROM tableb -- unique columns from another table
END IF;
END IF;
END;
$$;
I come from a MS SQL Server background where I don't have to specify in the CREATE FUNCTIONstatement what I'm returning, hence this is very confusing for me.
Not an answer, but an explanation of why answer from #JonathanJacobson will not work using a simple example:
\d animals
Table "public.animals"
Column | Type | Collation | Nullable | Default
--------+------------------------+-----------+----------+---------
id | integer | | not null |
cond | character varying(200) | | not null |
animal | character varying(200) | | not null |
CREATE OR REPLACE FUNCTION public.animal(a_type character varying)
RETURNS record
LANGUAGE plpgsql
AS $function$
BEGIN
SELECT row(id, cond, animal) FROM animals where animal = a_type;
END;
$function$
select * from animal('cat');
ERROR: a column definition list is required for functions returning "record"
LINE 1: select * from animal('cat');
CREATE OR REPLACE FUNCTION public.animal(a_type character varying)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT id, cond, animal FROM animals where animal = a_type;
END;
$function$
;
select * from animal('cat') as t(i integer, c varchar, a varchar);
i | c | a
---+------+-----
1 | fat | cat
2 | slim | cat
6 | big | cat
In order to use the output of a function returning a record or setof record you need to declare the output fields and types when you run the function.
You could use the record type. Not tested.
CREATE OR REPLACE FUNCTION functiona(_url character varying DEFAULT NULL)
RETURNS record
LANGUAGE plpgsql
AS
$$
DECLARE
_broadcasttypeid int;
BEGIN
IF _url IS NOT NULL
THEN
_urltypeid := reference.urltype(_url);
IF _urltypeid = 1
THEN
RETURN
(SELECT row(location, auxiliary, response) FROM tablea);
END IF;
IF _urltypeid = 2
THEN
RETURN
(SELECT row(ip, location, host, authority) FROM tableb);
END IF;
END IF;
END;
$$;
Other composite types, such as jsonb and hstore are also a solution.

Rename the column name of a stored function

I've got a postgresql stored procedure, which is returning an integer.
When I call that function, the result is returned with the function name as column name.
For example the name of the function is: "add-person". The column name, when invoking the function, is "add-person".
Is there a way to make the database return the integer with a self-choosen column name? For example "id"?
I think it is pretty easy, but I currently miss the forests for the trees..
Edit:
What i'd missed to tell, is that the return value is a variable, like so:
CREATE OR REPLACE FUNCTION "scheme"."add-person"(arggivenname character varying, argfamilyname character varying) RETURNS integer AS
$BODY$
DECLARE
varResponse integer;
BEGIN
-- Operations before
INSERT INTO "scheme"."table"
(
given_name,
family_name
)
VALUES
(
arggivenname,
argfamilyname
)
RETURNING
"id"
INTO
varResponse;
-- Operations after
RETURN varResponse;
END;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
You can us the AS statement for that. That means:
Select add-person() AS yourcolumnname
To have a named column from a function it is necessary to create a type and return that type from the function
create type mytype as (mycolumn integer);
create or replace function ri()
returns mytype as $$
select 1;
$$ language sql;
select * from ri();
mycolumn
----------
1
Edit
Or much simpler without the type creation as in #pozs comment:
create or replace function ri(out mycolumn integer)
as $$
select 1;
$$ language sql;

PostgreSQL modifying fields dynamically in NEW record in a trigger function

I have a user table with IDs and usernames (and other details) and several other tables referring to this table with various column names (CONSTRAINT some_name FOREIGN KEY (columnname) REFERENCES "user" (userid)). What I need to do is add the usernames to the referring tables (in preparation for dropping the whole user table). This is of course easily accomplished with a single ALTER TABLE and UPDATE, and keeping these up-to-date with triggers is also (fairly) easy. But it's the trigger function that is causing me some annoyance. I could have used individual functions for each table, but this seemed redundant, so I created one common function for this purpose:
CREATE OR REPLACE FUNCTION public.add_username() RETURNS trigger AS
$BODY$
DECLARE
sourcefield text;
targetfield text;
username text;
existing text;
BEGIN
IF (TG_NARGS != 2) THEN
RAISE EXCEPTION 'Need source field and target field parameters';
END IF;
sourcefield = TG_ARGV[0];
targetfield = TG_ARGV[1];
EXECUTE 'SELECT username FROM "user" WHERE userid = ($1).' || sourcefield INTO username USING NEW;
EXECUTE format('SELECT ($1).%I', targetfield) INTO existing USING NEW;
IF ((TG_OP = 'INSERT' AND existing IS NULL) OR (TG_OP = 'UPDATE' AND (existing IS NULL OR username != existing))) THEN
CASE targetfield
WHEN 'username' THEN
NEW.username := username;
WHEN 'modifiername' THEN
NEW.modifiername := username;
WHEN 'creatorname' THEN
NEW.creatorname := username;
.....
END CASE;
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE;
And using the trigger function:
CREATE TRIGGER some_trigger_name BEFORE UPDATE OR INSERT ON my_schema.my_table FOR EACH ROW EXECUTE PROCEDURE public.add_username('userid', 'username');
The way this works is the trigger function receives the original source field name (for example userid) and the target field name (username) via TG_ARGV. These are then used to fill in the (possibly) missing information. All this works nice enough, but how can I get rid of that CASE-mess? Is there a way to dynamically modify the values in the NEW record when I don't know the name of the field in advance (or rather it can be a lot of things)? It is in the targetfield parameter, but obviously NEW.targetfield does not work, nor something like NEW[targetfield] (like Javascript for example).
Any ideas how this could be accomplished? Besides using for instance PL/Python..
There are not simple plpgsql based solutions. Some possible solutions:
Using hstore extension.
CREATE TYPE footype AS (a int, b int, c int);
postgres=# select row(10,20,30);
row
------------
(10,20,30)
(1 row)
postgres=# select row(10,20,30)::footype #= 'b=>100';
?column?
-------------
(10,100,30)
(1 row)
hstore based function can be very simple:
create or replace function update_fields(r anyelement,
variadic changes text[])
returns anyelement as $$
select $1 #= hstore($2);
$$ language sql;
postgres=# select *
from update_fields(row(10,20,30)::footype,
'b', '1000', 'c', '800');
a | b | c
----+------+-----
10 | 1000 | 800
(1 row)
Some years ago I wrote a extension pl toolbox. There is a function record_set_fields:
pavel=# select * from pst.record_expand(pst.record_set_fields(row(10,20),'f1',33));
name | value | typ
------+-------+---------
f1 | 33 | integer
f2 | 20 | integer
(2 rows)
Probably you can find some plpgsql only solutions based on some tricks with system tables and arrays like this, but I cannot to suggest it. It is too less readable and for not advanced user just only black magic. hstore is simple and almost everywhere so it should be preferred way.
On PostgreSQL 9.4 (maybe 9.3) you can try to black magic with JSON manipulations:
postgres=# select json_populate_record(NULL::footype, jo)
from (select json_object(array_agg(key),
array_agg(case key when 'b'
then 1000::text
else value
end)) jo
from json_each_text(row_to_json(row(10,20,30)::footype))) x;
json_populate_record
----------------------
(10,1000,30)
(1 row)
So I am able to write function:
CREATE OR REPLACE FUNCTION public.update_field(r anyelement,
fn text, val text,
OUT result anyelement)
RETURNS anyelement
LANGUAGE plpgsql
AS $function$
declare jo json;
begin
jo := (select json_object(array_agg(key),
array_agg(case key when 'b' then val
else value end))
from json_each_text(row_to_json(r)));
result := json_populate_record(r, jo);
end;
$function$
postgres=# select * from update_field(row(10,20,30)::footype, 'b', '1000');
a | b | c
----+------+----
10 | 1000 | 30
(1 row)
JSON based function should not be terrible fast. hstore should be faster.
UPDATE/caution: Erwin points out that this is currently undocumented, and the docs indicates it should not be possible to alter records this way.
Use Pavel's solution or hstore.
The json based solution is almost as fast as hstore when simplified. json_populate_record() modifies existing records for us, so we only have to create a json object from the keys we want to change.
See my similar answer, where you'll find benchmarks that compares the solutions.
The simplest solution requires Postgres 9.4:
SELECT json_populate_record (
record
,json_build_object('key', 'new-value')
);
But if you only have Postgres 9.3, you can use casting instead of json_object:
SELECT json_populate_record(
record
, ('{"'||'key'||'":"'||'new-value'||'"}')::json
);

how to implement contains operator in Postgres

How to implement contains operator for strings which returns true if left string contains in right string.
Operator name can be any. I tried ## and code below but
select 'A' ## 'SAS'
returns false.
How to fix ?
CREATE OR REPLACE FUNCTION public.contains(searchFor text, searchIn text)
RETURNS bool
AS $BODY$ BEGIN
RETURN position( searchFor in searchIn)<>0;
END; $BODY$ language plpgsql immutable RETURNS NULL ON NULL INPUT;
CREATE OPERATOR public.## (
leftarg = text,
rightarg = text,
procedure = public.contains
);
Using Postgres 9.1 and above in windows and linux.
select contains('A' , 'SAS' )
returns true as expected.
Update
I tried in 9.1 code from answer:
CREATE OR REPLACE FUNCTION public.contains(searchFor text, searchIn text)
RETURNS bool
LANGUAGE sql IMMUTABLE
AS $BODY$
SELECT position( searchFor in searchIn )<>0;
$BODY$;
CREATE OPERATOR public.<# (
leftarg = text,
rightarg = text,
procedure = public.contains
);
but got error
ERROR: column "searchin" does not exist
LINE 5: SELECT position( searchFor in searchIn )<>0;
How to make it work in 9.1 ? In 9.3 it works.
Using
"PostgreSQL 9.1.2 on x86_64-unknown-linux-gnu, compiled by gcc-4.4.real (Debian 4.4.5-8) 4.4.5, 64-bit"
PostgreSQL already defines an ## operator on (text,text) in the pg_catalog schema, which is defined as:
regress=> \do ##
List of operators
Schema | Name | Left arg type | Right arg type | Result type | Description
------------+------+---------------+----------------+-------------+-------------------
pg_catalog | ## | text | text | boolean | text search match
That is taking precedence over the ## you defined in the public schema.
I suggest using the operator <# for contains, because that's consistent with the array contains and contained-by operators. There's no usage of it in pg_catalog for text ... but there's no guarantee one won't be added in a future version or by an extension.
If you want to guarantee that your operators take precedence over those in pg_catalog, you need to put them in a different schema and put it first on the search path, explicitly before pg_catalog. So something like:
CREATE SCHEMA my_operators;
CREATE OR REPLACE FUNCTION my_operators.contains(searchFor text, searchIn text)
RETURNS bool
LANGUAGE sql IMMUTABLE
AS $BODY$
SELECT position( searchFor in searchIn)<>0;
$BODY$;
CREATE OPERATOR my_operators.<# (
leftarg = text,
rightarg = text,
procedure = public.contains
);
then
SET search_path = 'my_operators, pg_catalog, public';
which you can do with ALTER USER and/or ALTER DATABASE to make it a default.

Using pgsql_fdw in a function result "ERROR: cache lookup failed for type 0"

We want to use pgsql_fdw to select a table of remote postgresql database. When we
select the table in a session it is okay, but when we use the foreign table in a funciton
it turns out "ERROR: cache lookup failed for type 0". Does anybody know why?
1. base information
skytf=> \d ft_test;
Foreign table "skytf.ft_test"
Column | Type | Modifiers
--------+-----------------------+-----------
id | integer |
name | character varying(32) |
Server: pgsql_srv
skytf=> \des+ pgsql_srv
List of foreign servers
Name | Owner | Foreign-data wrapper | Access privileges | Type | Version | Options
-----------+-------+----------------------+-------------------+------+---------+----------------------------------------
pgsql_srv | skytf | pgsql_fdw | | | | {host=127.0.0.1,port=1923,dbname=mydb}
(1 row)
2. destination table
mydb=> \d test
Table "mydb.test"
Column | Type | Modifiers
--------+-----------------------+-----------
id | integer |
name | character varying(32) |
Indexes:
"idx_test_1" btree (id)
3. function
CREATE or replace FUNCTION func_sync_bill() RETURNS INTEGER AS $$
BEGIN
begin
insert into test_tf (id,name) select id,name from ft_test;
return 1;
end;
END;
$$ LANGUAGE 'plpgsql';
4. it works in a session
skytf=> create table test_tf(id integer,name varchar(32));
CREATE TABLE
skytf=> insert into test_tf select * from ft_test;
INSERT 0 1990000
5. function call error
skytf=> truncate table test_tf;
TRUNCATE TABLE
skytf=> select func_sync_bill();
ERROR: cache lookup failed for type 0
CONTEXT: SQL statement "insert into test_tf (id,name) select id,name from ft_test"
PL/pgSQL function "func_sync_bill" line 5 at SQL statement
When I call the function func_sync_bill() which will select a foreign table, it generates the the error.
Is this a bug of pgsql_fdw?
Looks like a bug in the foreign data wrapper or plpgsql or something. Given that the \d commands are working, I would expect that the catalogs are fine, which suggests that the real problem is data corruption although the fact that it works outside the function suggests that maybe that's not the case.
things to do to troubleshoot would be to try to rewrite your function as an SQL language function instead of plpgsql and see if that fixes the problem. if it does, you have a workaround. Also try on more recent minor versions of PostgreSQL. If the problem is fixed, then great. But if not, this is a case where a bug report to the project may result in getting the problem fixed.
Finaly I use dynamic statements avoid the problem.
--dynamic statements
skytf=> CREATE or replace FUNCTION func_sync_bill() RETURNS INTEGER AS $$
skytf$> BEGIN
skytf$> begin
skytf$> EXECUTE 'insert into test_tf (id,name) select id,name from ft_test';
skytf$> return 0;
skytf$> end;
skytf$> END;
skytf$> $$ LANGUAGE 'plpgsql';
CREATE FUNCTION
--try again
skytf=> truncate table test_tf;
TRUNCATE TABLE
skytf=> select func_sync_bill();
func_sync_bill
----------------
0
(1 row)
skytf=> select count(*) from test_tf;
count
---------
1990000
(1 row)