是否可以避免plpgsql函数中复合类型的显式转换?

时间:2012-04-07 08:56:21

标签: postgresql plpgsql

我正在开发一个框架,为PostgreSQL 9.1上的内容存储动态创建表。其中一个API函数允许调用者通过指定给定对象(例如,Web表单)中的所有字段来保存新的内容条目。为了接收一组字段框架,创建一个复合类型。

请考虑以下代码:

CREATE SEQUENCE seq_contents MINVALUE 10000;
CREATE TABLE contents (
    content_id      int8        not null,
    is_edited       boolean     not null default false,
    is_published    boolean     not null default false,
    "Input1"        varchar(60),
    "CheckBox1"     int2,
    "TheBox"        varchar(60),
    "Slider1"       varchar(60)
);
CREATE TYPE "contentsType" AS (
    "Input1"        varchar(60),
    "CheckBox1"     int2,
    "TheBox"        varchar(60),
    "Slider1"       varchar(60)
);
CREATE OR REPLACE FUNCTION push(in_all anyelement) RETURNS int8 AS $push$
DECLARE
    _c_id   int8;
BEGIN
    SELECT nextval('seq_contents') INTO _c_id;

    EXECUTE $$INSERT INTO contents
    SELECT a.*, b.*
      FROM (SELECT $1, true, false) AS a,
           (SELECT $2.*) AS b$$ USING _c_id, in_all;

    RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;

现在,为了调用此函数,我必须添加显式强制转换,如下所示:

SELECT push(('input1',1,'thebox','slider1')::"contentsType");

有没有办法避免显式演员?因为我希望外部调用者不要处理强制转换,即隐藏PostgreSQL函数背后的逻辑。目前我有这样的错误:

SELECT push(('input1',1,'thebox','slider1'));
ERROR:  PL/pgSQL functions cannot accept type record
CONTEXT:  compilation of PL/pgSQL function "push" near line 1

2 个答案:

答案 0 :(得分:1)

由于您要对要插入的表名进行硬编码,并且您需要固定数量和类型的参数,因此我不清楚为什么需要“contentsType”类型。为什么不从函数调用中消除额外的括号,只是直接传递这四个参数?这让一切变得更简单。

CREATE OR REPLACE FUNCTION push(
    "Input1"        varchar(60),
    "CheckBox1"     int2,
    "TheBox"        varchar(60),
    "Slider1"       varchar(60)
) RETURNS int8 AS $push$
DECLARE
    _c_id   int8;
BEGIN
    SELECT nextval('seq_contents') INTO _c_id;

    EXECUTE $$INSERT INTO contents
    VALUES ($1, true, false, $2, %3, %4, $5)
      $$ USING _c_id, "Input1", "CheckBox1", "TheBox", "Slider1");

    RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;

这使得调用函数看起来像这样:

SELECT push('input1',1,'thebox','slider1');

如果您想要推广push()函数以使其适用于所有表格,那么如果超过此问题,您将遇到其他问题。您将无法通过该函数在执行期间需要知道表名的事实。如果要重载函数以便为每种记录类型分别使用push(),则需要以某种方式提供有关记录类型的信息。所以,如果你想做这样的事情,你问题的简短回答是“不。”

另一方面,你可能会让它变得比它需要的更难。我希望您知道自动为每个表创建一个类型,与表名称相同。您可以利用它来避免显式声明类型并传递与表相同名称的记录 - 使用函数将填充的值的虚拟条目。我认为你可以制作一个完全通用的推送功能,虽然它可能很难超越plpgsql中的强类型问题;如果你熟悉它,用C编写函数可能会更容易。

答案 1 :(得分:1)

您是否考虑过将记录变量作为文字表示? 理论上,每个记录变量都可以使用普通的CAST运算符转换为文本。

以下是修改过的函数,以便in_all具有类型文本并在USING子句中转换为"contentsType"

CREATE OR REPLACE FUNCTION push(in_all text) RETURNS int8 AS $push$
DECLARE
    _c_id   int8;
BEGIN
    SELECT nextval('seq_contents') INTO _c_id;

    EXECUTE $$INSERT INTO contents
    SELECT a.*, b.*
      FROM (SELECT $1, true, false) AS a,
           (SELECT $2.*) AS b$$ USING _c_id, in_all::"contentsType";

    RETURN _c_id;
END;
$push$ LANGUAGE plpgsql;

然后可以这样调用它(没有明确的类型引用)

select push( '(input1,1,thebox,slider1)' );

或类似(明确记录到文本)

SELECT push(('input1',1,'thebox','slider1')::"contentsType"::text);

这不仅适用于“contentsType”,而是任何其他记录类型,假设该函数能够将其转换回该类型。

同样在plpgsql中,我认为这也应该有效:

   ret := push(r::text);

当r是记录变量时。