Postgresql

PostgreSQL 在某些情況下會忽略隱式轉換

  • July 27, 2020

設置(僅在 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);

正如預期的那樣,這些工作正常:

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

但這些不會:

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

根據文件

如果強制轉換標記為 AS IMPLICIT,則可以在任何上下文中隱式呼叫它,無論是賦值還是在表達式內部。

那麼為什麼會出現錯誤呢?

區別在於:

在前兩個(工作)範例中,'a'is 只是一個無類型的字元串文字- 這與varchar. enum列舉類型的基本輸入/輸出函式提供了到您的自定義類型的轉換。(系統目錄中沒有明確的條目pg_cast。) Postgres 確定數據類型的最佳匹配,因為使用者沒有提供。甚至不需要您的自定義演員表。既不用於類型解析,也不用於運算符解析。

在以下範例中,您傳遞了一個類型化的值( varchar)。現在,您的自定義演員可能會派上用場。例如,如果沒有您的自定義演員表,這將無法工作:

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

您的範例不是簡單的分配,而是帶有 operator 的表達式=。我們處於Operator Type Resolution的領域。第 2a 項規定:

如果二元運算符呼叫的一個參數是未知類型,則假定它與此檢查的另一個參數的類型相同。

這將涵蓋my_enum_column = 'foo'- 使用無類型文字。

但是沒有什麼可以伸展到覆蓋my_enum_column = 'foo'::varchar。似乎只有=左右操作數的通用運算符anyenum可用這一事實不足以在運算符解析中提供線索:

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

您必須為此註冊一個更明確的運算符。但我不會那樣做。我會做類似的事情(還要注意各種正交語法修復):

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<>在這裡擺弄

或者只是添加明確的演員表。這是最簡單和最安全的。手冊警告(在您引用的同一頁面上):

保守地將演員表標記為隱式是明智的。…

還有更多 - 推薦閱讀。

如果您必須創建該自定義轉換(甚至添加一個運算符),請考慮使用text而不是varchar,它是字元串類型中的“首選類型”,因此對於可能的極端情況問題更加穩健。

引用自:https://dba.stackexchange.com/questions/271670