Is there a way to disable function overloading in Postgres - postgresql

My users and I do not use function overloading in PL/pgSQL. We always have one function per (schema, name) tuple. As such, we'd like to drop a function by name only, change its signature without having to drop it first, etc. Consider for example, the following function:
CREATE OR REPLACE FUNCTION myfunc(day_number SMALLINT)
RETURNS TABLE(a INT)
AS
$BODY$
BEGIN
RETURN QUERY (SELECT 1 AS a);
END;
$BODY$
LANGUAGE plpgsql;
To save time, we would like to invoke it as follows, without qualifying 1 with ::SMALLINT, because there is only one function named myfunc, and it has exactly one parameter named day_number:
SELECT * FROM myfunc(day_number := 1)
There is no ambiguity, and the value 1 is consistent with SMALLINT type, yet PostgreSQL complains:
SELECT * FROM myfunc(day_number := 1);
ERROR: function myfunc(day_number := integer) does not exist
LINE 12: SELECT * FROM myfunc(day_number := 1);
^
HINT: No function matches the given name and argument types.
You might need to add explicit type casts.
When we invoke such functions from Python, we use a wrapper that looks up functions' signatures and qualifies parameters with types. This approach works, but there seems to be a potential for improvement.
Is there a way to turn off function overloading altogether?

Erwin sent a correct reply. My next reply is related to possibility to disable overloading.
It is not possible to disable overloading - this is a base feature of PostgreSQL function API system - and cannot be disabled. We know so there are some side effects like strong function signature rigidity - but it is protection against some unpleasant side effects when function is used in Views, table definitions, .. So you cannot to disable it.
You can simply check if you have or have not overloaded functions:
postgres=# select count(*), proname
from pg_proc
where pronamespace <> 11
group by proname
having count(*) > 1;
count | proname
-------+---------
(0 rows)

This is actually not directly a matter of function overloading (which would be impossible to "turn off"). It's a matter of function type resolution. (Of course, that algorithm could be more permissive without overloaded functions.)
All of these would just work:
SELECT * FROM myfunc(day_number := '1');
SELECT * FROM myfunc('1'); -- note the quotes
SELECT * FROM myfunc(1::smallint);
SELECT * FROM myfunc('1'::smallint);
Why?
The last two are rather obvious, you mentioned that in your question already.
The first two are more interesting, the explanation is buried in the Function Type Resolution:
unknown literals are assumed to be convertible to anything for this purpose.
And that should be the simple solution for you: use string literals.
An untyped literal '1' (with quotes) or "string literal" as defined in the SQL standard is different in nature from a typed literal (or constant).
A numeric constant 1 (without quotes) is cast to a numeric type immediately. The manual:
A numeric constant that contains neither a decimal point nor an
exponent is initially presumed to be type integer if its value fits in
type integer (32 bits); otherwise it is presumed to be type bigint if
its value fits in type bigint (64 bits); otherwise it is taken to be
type numeric. Constants that contain decimal points and/or exponents
are always initially presumed to be type numeric.
The initially assigned data type of a numeric constant is just a
starting point for the type resolution algorithms. In most cases the
constant will be automatically coerced to the most appropriate type
depending on context. When necessary, you can force a numeric value to
be interpreted as a specific data type by casting it.
Bold emphasis mine.
The assignment in the function call (day_number := 1) is a special case, the data type of day_number is unknown at this point. Postgres cannot derive a data type from this assignment and defaults to integer.
Consequently, Postgres looks for a function taking an integer first. Then for functions taking a type only an implicit cast away from integer, in other words:
SELECT casttarget::regtype
FROM pg_cast
WHERE castsource = 'int'::regtype
AND castcontext = 'i';
All of these would be found - and conflict if there were more than one function. That would be function overloading, and you would get a different error message. With two candidate functions like this:
SELECT * FROM myfunc(1);
ERROR: function myfunc(integer) is not unique
Note the "integer" in the message: the numeric constant has been cast to integer.
However, the cast from integer to smallint is "only" an assignment cast. And that's where the journey ends:
No function matches the given name and argument types.
SQL Fiddle.
More detailed explanation in these related answers:
PostgreSQL ERROR: function to_tsvector(character varying, unknown) does not exist
Generate series of dates - using date type as input
Dirty fix
You could fix this by "upgrading" the cast from integer to smallint to an implicit cast:
UPDATE pg_cast
SET castcontext = 'i'
WHERE castsource = 'int'::regtype
AND casttarget = 'int2'::regtype;
But I would strongly discourage tampering with the default casting system. Only consider this if you know exactly what you are doing. You'll find related discussions in the Postgres lists. It can have all kinds of side effects, starting with function type resolution, but not ending there.
Aside
Function type resolution is completely independent from the used language. An SQL function would compete with PL/perl or PL/pgSQL or "internal" functions just the same. The function signature is essential. Built-in functions only come first, because pg_catalog comes first in the default search_path.

There are plenty of in built functions that are overloaded, so it simply would not work if you turned off function overloading.

Related

Checking if a range contains a value broken in PQexecPrepared (works in psql)

I have this (rather ugly, generated) prepared statement to fetch some game data. I try to check if a value ($3) is contained in spawn_level_range (which is an int4range) by doing $3<#quests.spawn_level_range:
SELECT quests.id,
quests.base_attack,
quests.base_strg,
quests.base_accy,
quests.base_hp,
quests.name,
quests.task,
quests.image_url,
quests.spawn_chance
FROM quests
WHERE (((quests.server_id=$1)
AND ((quests.channel_id='all') OR (quests.channel_id=$2)))
AND ($3<#quests.spawn_level_range))
ORDER BY RANDOM()
LIMIT 1;
This exact query works perfectly when pasted into psql when I prepend:
prepare test (varchar, varchar, int) AS
then run it with:
execute test('669105577238069249', '682205516667158549', 1);
However, for some reason, it just does not work in libpq.
When running the statement with PQexecPrepared, it raises the error:
ERROR: malformed range literal: "1"
DETAIL: Missing left parenthesis or bracket.
(note that 1 is what I'm trying to bind $3 to)
It seems like it's trying to interpret $3 as a range (rather than an integer) – which seems like a bug to me.
In your prepared statement, you explicitly declare the third parameter to be an integer.
In your PQprepare call (that you didn't show) you must have neglected to set the paramTypes argument to indicate the types of the parameters, so they are all unknown to PostgreSQL, and it infers the data type from the context.
Now there are two operators <# for ranges:
anyrange <# anyrange
anyelement <# anyrange
Not knowing which one you want, PostgreSQL's data type resolution rules prefer the operator that has the same data type on both sides.
There are two possible solutions:
specify the correct type in the paramTypes argument of PQprepare
add an explicit type cast to the query: CAST ($3 AS integer)

A function call misbehaves in PostgreSQL

I use PostgreSQL 10.3.
I have created the following domains:
CREATE DOMAIN common.citext_nullable
AS extensions.citext;
CREATE DOMAIN common.citext_not_null
AS extensions.citext NOT NULL;
CREATE DOMAIN common.smallint_ge_zero_nullable
AS smallint;
ALTER DOMAIN common.smallint_ge_zero_nullable
ADD CONSTRAINT smallint_ge_zero_nullable_check CHECK (value >= 0);
and the following function:
CREATE OR REPLACE FUNCTION common.fun_name(
p_1 common.citext_not_null,
p_2 common.citext_nullable,
p_3 common.citext_nullable,
p_4 common.smallint_ge_zero_nullable)
RETURNS ...
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
...
BEGIN
...
END;
$BODY$;
Notes:
All parameters/arguments are of domain types.
Domains and functions are in the same schema "common".
The schema "common" is included in the search path.
All extensions are in the schema "extensions".
The schema "extensions" is also included in the search path.
"citext"-based domains work as expected.
"smallint"-based domain works strangely.
The above domains and function are simplified for the scope of the question.
I can call the function either by
SELECT fun_name('any', 'any', 'any', 5::smallint_ge_zero_nullable);
or even by
SELECT fun_name('any', 'any', 'any', '5');
but I cannot call it by:
SELECT fun_name('any', 'any', 'any', 5);
I get the following error:
SQL Error [42883]: ERROR: function fun_name(unknown, unknown, unknown, integer) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Position: 8
Why "citext"-based arguments are shown as "unknown"? As per doc, page 1431
argtype
The data type(s) of the function's arguments (optionally schema-qualified), if any. The argument types can be base, composite, or domain types, or ...
(It is "funny" the "unknown" arguments, in the end, to be accepted and work as expected and the "integer" argument not to be accepted and behave strangely.)
This behavior is related to int - smallint casting and not to the domain.
You can find the rules for associating a function call to the function with the proper parameters here. It will use implicit cast when available and will always match 'unknown' types to anything. Since you have only one signature for your function, case 1 (explicit cast) and 2 (all unknown) will be matched to your function.
There is no automatic down casting, so integer -> smallInt won't occur implicitely. Let's think about a function having two signatures f(input as int) and f(input as smallint) If downscasting was to occur, which one should be used when calling f(5)? This mailing-list thread will give more details.
So the solutions are to either
- do the explicit casting (case 1)
- or to have a function wrapper with the generic types (integer) that do the casting for you (and handles errors..)
- or to call the function with the output of a table column having the proper type.

Errors with an easy PL/pgSQL Function

I'm trying to write my first PL/pgSQL function. For right now it is simply supposed to return the number of characters in a value that is passed to it.
CREATE OR REPLACE FUNCTION public.cents(money)
RETURNS int
LANGUAGE plpgsql
LEAKPROOF
AS $function$
DECLARE
new_price money;
size int;
BEGIN
size := char_length(money);
RETURN size;
END;
$function$;
When I try to test with $66.66 I get one error:
select cents($66.66);
ERROR: syntax error at or near ".66"
LINE 1: select cents($66.66);
^
And if I use $66 I get a different error:
select cents($66);
ERROR: there is no parameter $66
LINE 1: select cents($66);
^
Using just the integer 66 gives me a third error:
select cents(66);
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
What am I doing wrong here?
Are you sure you want to use the data type money? Consider:
PostgreSQL: Which Datatype should be used for Currency?
If you need the type money, be sure to understand the role of locale settings for this type. Read the manual.
You can't enter literals without single quotes (like numeric constants) - unless you cast them, which would be inefficient. But that's not the only error. Your function would work like this:
CREATE OR REPLACE FUNCTION public.cents(_my_money_parameter money)
RETURNS int AS
$func$
BEGIN
RETURN char_length(_my_money_parameter::text);
END
$func$ LANGUAGE plpgsql LEAKPROOF;
Call:
SELECT public.cents(money '66.66');
SELECT public.cents('66.66'::money);
SELECT public.cents(66.66::money);
The 1st call variant is the most efficient, but it depends on locale settings. The dot in my example is interpreted as thousands separator and ignored (not as decimal point) in some locales.
Notes
You treat money like a parameter name in the function body, but it's just the data type. If you want to use parameter names, you have to declare them like demonstrated. Or refer to parameters with positional references: $1, $2 etc.
char_length() expects a character data type, you cannot use it for data type money without casting. Just length() is equivalent.
If you include the dollar sign, you need single quotes for the string literal: '$66.66' - and the format must match your locale setting to work for money.
If you just supply the numeric constant 66, Postgres won't find the function with a money parameter due to the rules of function type resolution. Details:
Is there a way to disable function overloading in Postgres
Start by reading the chapter Constants in the manual.
Continue with the page on CREATE FUNCTION.
You need to put single-quotes around your input:
SELECT cents('$66.66');
If your settings don't allow this (still throw an error) you can try casting:
SELECT cents('66.66'::float8::numeric::money);
Be sure to reference the docs as they provide a good overview:
https://www.postgresql.org/docs/current/static/datatype-money.html

PostgreSQL syntax error in parameterized query on "date $1"

Trying to parameterize my SQL queries (using libpq function PQexecParams), I was stuck on a syntax error:
SELECT date $1
The error is:
ERROR: syntax error at or near "$1"
Prepared statements
The explanation for this can be found in the chapter Constants of Other Types of the manual:
The ::, CAST(), and function-call syntaxes can also be used to specify
run-time type conversions of arbitrary expressions, as discussed in
Section 4.2.9. To avoid syntactic ambiguity, the type 'string' syntax
can only be used to specify the type of a simple literal constant.
Another restriction on the type 'string' syntax is that it does not
work for array types; use :: or CAST() to specify the type of an array
constant.
Bold emphasis mine.
Parameters for prepared statements are not actually sting literals but typed values, so you cannot use the form type 'string'. Use one of the other two forms to cast the value to a different type, like you found yourself already.
Example:
PREPARE foo AS SELECT $1::date;
EXECUTE foo('2005-1-1');
Similar for PQexecParams in the libpq C library
The documentation:
... In the SQL command text, attach an explicit cast to the parameter
symbol to show what data type you will send. For example:
SELECT * FROM mytable WHERE x = $1::bigint;
This forces parameter $1 to be treated as bigint, whereas by default
it would be assigned the same type as x. Forcing the parameter type
decision, either this way or by specifying a numeric type OID, is
strongly recommended. ...
The alternative, as mentioned in the quote above, is to pass the OIDs of respective data types with paramTypes[] - if you actually need the cast. In most cases it should work just fine to let Postgres derive data types from the query context.
paramTypes[]
Specifies, by OID, the data types to be assigned to the parameter
symbols. If paramTypes is NULL, or any particular element in the array
is zero, the server infers a data type for the parameter symbol in the
same way it would do for an untyped literal string.
You can get the OID of data types from the system catalog pg_type:
SELECT oid FROM pg_type WHERE typname = 'date';
You must use the correct internal type name. For instance: int4 for integer.
Or with a convenience cast to regtype:
SELECT 'date'::regtype::oid;
This is more flexible as known aliases for the type name are accepted as well. For instance: int4, int or integer for integer.
The solution is to use a type cast instead of date:
SELECT $1::date

Disable implicit conversion of quoted values to integer

# CREATE TABLE foo ( id serial, val integer );
CREATE TABLE
# INSERT INTO foo (val) VALUES ('1'), (2);
INSERT 0 2
# SELECT * FROM foo WHERE id='1';
id | val
----+-----
1 | 1
(1 row)
Here, both on insert and on selection postgres implicitly converts quoted strings to integral types rather than raise a type error, unless the quoted value is very specifically typed as a varchar:
# INSERT INTO foo (val) VALUES (varchar '1');
ERROR: column "val" is of type integer
but expression is of type character varying
LINE 1: INSERT INTO foo (val) VALUES (varchar '1');
^
HINT: You will need to rewrite or cast the expression.
The issue here is for dynamically typed languages without implicit conversions (e.g. Ruby or Python)
a quoted value maps to a string
an integer maps to an integer
those are not compatible so depending on the connecting application's architecture this behavior may lead to incoherent caches and the like
Is there a way to disable it and force quoted values to always be varchars (unless explicitly convert)?
edit: because people apparently focus on the irrelevant, these queries come from parameterized statements, psycopg2 will convert strings to quoted values and quoted values back to strings, so the mismatch exists regardless of access method, that's a red herring. here's the exact same thing with parameterised statements:
import psycopg2.extensions
with psycopg2.connect(dbname='postgres') as cn:
cn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
with cn.cursor() as cx:
cx.execute("DROP DATABASE IF EXISTS test")
cx.execute("CREATE DATABASE test")
with psycopg2.connect(dbname='test') as cn:
with cn.cursor() as cx:
cx.execute("CREATE TABLE foo ( id serial, val integer )")
cx.execute("INSERT INTO foo (val) VALUES (%s), (%s)",
(1, '2'))
cx.execute("SELECT * FROM foo WHERE id=%s",
('1',))
print cx.fetchall()
which outputs:
[(1, 1)]
No, you cannot disable implicit conversion of quoted literals to any target type. PostgreSQL considers such literals to be of unknown type unless overridden by a cast or literal type-specifier, and will convert from unknown to any type. There is no cast from unknown to a type in pg_cast; it's implicit. So you can't drop it.
As far as I know, PostgreSQL is following the SQL spec by accepting quoted literals as integers.
To PostgreSQL's type engine, 1 is an integer, and '1' is an unknown that's type-inferred to an integer if passed to an integer function, operator, or field. You cannot disable type inference from unknown or force unknown to be treated as text without hacking the parser / query planner directly.
What you should be doing is using parameterised statements instead of substituting literals into SQL. You won't have this issue if you do so, because the client-side type is known or can be specified. That certainly works with Python (psycopg2) and Ruby (Pg gem) doesn't work how I thought for psycopg2, see below.
Update after question clarification: In the narrow case being described here, psycopg2's client-side parameterised statements, while correct, do not produce the result the original poster desires. Running the demo in the update shows that psycopg2 isn't using PostgreSQL's v3 bind/execute protocol, it's using the simple query protocol and doing parameter substitution locally. So while you're using parameterised statements in Python, you're not using parameterised statements in PostgreSQL. I was mistaken above in saying that parameterised statments in psycopg2 would resolve this issue.
The demo runs this SQL, from the PostgreSQL logs:
< 2014-07-07 18:17:24.450 WST >LOG: statement: INSERT INTO foo (val) VALUES (1), ('2')
< 2014-07-07 18:17:24.451 WST >LOG: statement: SELECT * FROM foo WHERE id='1'
Note the lack of placement parameters. They're substituted client-side.
So if you want psycopg2 to be stricter, you'll have to adapt the client side framework.
psycopg2 is extensible, so that should be pretty practical - you need to override the type handlers for str, unicode and integer (or, in Python3, bytes, str and integer) using psycopg2.extras, per adapting new types. There's even an FAQ entry about overriding psycopg2's handling of float as an example: http://initd.org/psycopg/docs/faq.html#faq-float
The naïve approach won't work though, because of infinite recursion:
def adapt_str_strict(thestr):
return psycopg2.extensions.AsIs('TEXT ' + psycopg2.extensions.adapt(thestr))
psycopg2.extensions.register_adapter(str, adapt_str_strict)
so you need to bypass type adapter registration to call the original underlying adapter for str. This will, though it's ugly:
def adapt_str_strict(thestr):
return psycopg2.extensions.AsIs('TEXT ' + str(psycopg2.extensions.QuotedString(thestr)))
psycopg2.extensions.register_adapter(str, adapt_str_strict)
Run your demo with that and you get:
psycopg2.ProgrammingError: parameter $1 of type text cannot be coerced to the expected type integer
HINT: You will need to rewrite or cast the expression.
(BTW, using server-side PREPARE and EXECUTE won't work, because you'll just suffer the same typing issues when passing values to EXECUTE via psycopg2).