Question

The setup (tried only on PostgreSQL 9.6):

CREATE TYPE MY_ENUM AS ENUM ('a', 'b');
CREATE CAST (CHARACTER VARYING AS MY_ENUM) WITH INOUT AS IMPLICIT;
CREATE TABLE t (x MY_ENUM);
INSERT INTO t VALUES ('a'::MY_ENUM), ('b'::MY_ENUM);

These work just fine, as expected:

INSERT INTO t VALUES ('a');
SELECT * FROM t WHERE x = 'a';

But these don't:

PREPARE p(CHARACTER VARYING) AS SELECT * FROM t WHERE x = $1;
;; error: operator does not exist: my_enum = character varying

CREATE FUNCTION f(ix CHARACTER VARYING, OUT ox MY_ENUM) AS
$$
    SELECT * FROM t WHERE x = ix
$$ LANGUAGE sql;
;; error: operator does not exist: my_enum = character varying

CREATE FUNCTION f(ix CHARACTER VARYING) RETURNS VOID AS
$$
BEGIN
    SELECT * FROM t WHERE input_type = ix;
END;
$$ LANGUAGE plpgsql;

SELECT f('piano');
;; error: operator does not exist: my_enum = character varying

According to the documentation:

If the cast is marked AS IMPLICIT then it can be invoked implicitly in any context, whether assignment or internally in an expression.

So why the errors?

Was it helpful?

Solution

The difference is this:

In the first two (working) examples, 'a' is just an untyped string literal - which is strictly different from varchar. The conversion to your custom enum type is provided by basic input/output functions of the enum type. (There is no explicit entry in the system catalog pg_cast.) Postgres determines the best match for a data type as the user did not provide one. Your custom cast is not even needed. Neither for type resolution nor for operator resolution.

In the following examples, you pass a typed value (varchar). Now, your custom cast may be of use. For example, this wouldn't work without your custom cast:

INSERT INTO t VALUES ('a'::varchar);

Your examples are not plain assignments, but expressions with the operator =. We are in the realm of Operator Type Resolution. Item 2a states:

If one argument of a binary operator invocation is of the unknown type, then assume it is the same type as the other argument for this check.

That would cover my_enum_column = 'foo' - with untyped literal.

But there is nothing that can be stretched to cover my_enum_column = 'foo'::varchar. Seems like the fact that only a generic = operator for left and right operand anyenum is available is not enough to clue in operator resolution:

-- missing operators:
SELECT oprleft::regtype, oprright::regtype, *
FROM   pg_operator
WHERE  oprname = '='
AND   ('anyenum'::regtype IN (oprleft, oprright) OR
       'my_enum'::regtype IN (oprleft, oprright));
 oprleft | oprright | ...
 --------+----------+----
 anyenum | anyenum  | ...
(1 row)

You would have to register a more explicit operator for this. But I wouldn't do that. I would do things like (also note various orthogonal syntax fixes):

PREPARE p(my_enum) AS SELECT * FROM t WHERE x = $1;  -- prepare with type my_enum
EXECUTE p('a'::varchar); -- then your cast kicks in
EXECUTE p('a'); -- untyped string literal works in any case
CREATE FUNCTION f1(ix my_enum, OUT ox my_enum)   -- again, declare enum
  RETURNS SETOF my_enum AS
$$
SELECT x FROM t WHERE x = ix
$$ LANGUAGE sql;

SELECT * FROM f1('a'::varchar);
CREATE FUNCTION f2(ix my_enum)  -- same here
  RETURNS SETOF t AS
$$
BEGIN
   RETURN QUERY
   SELECT * FROM t WHERE x = ix;
END;
$$ LANGUAGE plpgsql;

SELECT * FROM f2('a'::varchar);
SELECT * FROM f2('a');

db<>fiddle here

Or just add explicit casts. That's simplest and safest. The manual warns (on the same page you quoted):

It is wise to be conservative about marking casts as implicit. ...

There is more - recommended reading.

If you must create that custom cast (or even add an operator), consider using text instead of varchar, which is the "preferred type" among string types and hence more robust against possible corner-case issues.

Licensed under: CC-BY-SA with attribution
Not affiliated with dba.stackexchange
scroll top