获取在postgresql中返回记录的复杂输出类型的函数

时间:2015-05-06 09:30:28

标签: postgresql

我想在postgresql数据库中编写关于函数的精美详细的报告。 我构建了以下查询:

SELECT routine_name, data_type, proargnames
FROM   information_schema.routines
join pg_catalog.pg_proc on pg_catalog.pg_proc.proname = information_schema.routines.routine_name
WHERE specific_schema = 'public'
ORDER  BY routine_name;

它应该工作(基本上返回我想要的东西:函数名,输出数据类型和输入数据类型),除了一件事: 我有相对复杂的函数,其中许多函数返回record。 问题是,data_type也为这些函数返回record,而我想要详细的函数输出类型列表。 例如,我在我的一个函数中有类似的东西:

RETURNS TABLE("Res" integer, "Output" character varying) AS

如何在上面进行查询(或者,如果它可以解决问题,可能是新查询)返回类似的内容 这些功能integer, character varying代替record? 我正在使用postgresql 9.2
提前谢谢!

1 个答案:

答案 0 :(得分:1)

在运行时评估RECORD返回的值,无法以这种方式检索信息。

但是,如果使用RETURNS TABLE("Res" integer, "Output" character varying) AS,则有一个解决方案。

我使用的测试功能:

-- first function, uses RETURNS TABLE
CREATE FUNCTION test_ret(a TEXT, b TEXT) 
  RETURNS TABLE("Res" integer, "Output" character varying) AS $$
DECLARE                           
  ret RECORD;                                                                                   
BEGIN                           
  -- test
END;$$ LANGUAGE plpgsql;

-- second function, test some edge cases
-- same name as above, returns simple integer
CREATE FUNCTION test_ret(a TEXT)
  RETURNS INTEGER AS $$
DECLARE
  ret RECORD;
BEGIN
-- test
END;$$ LANGUAGE plpgsql;

如何检索此函数返回数据类型很容易,因为它存储在pg_catalog.pg_proc.proallargtypes中,问题是这是一个OID数组。我们必须删除此内容并将其加入pg_catalog.pg_types.oid

-- edit: add support for function not returning tables, thx Tommaso Di Bucchianico
WITH pg_proc_with_unnested_proallargtypes AS (
   SELECT
      pg_catalog.pg_proc.oid,
      pg_catalog.pg_proc.proname,
      CASE WHEN proallargtypes IS NOT NULL THEN unnest(proallargtypes) ELSE null END AS proallargtype
    FROM pg_catalog.pg_proc
      JOIN pg_catalog.pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_catalog.pg_namespace.oid
    WHERE pg_catalog.pg_namespace.nspname = 'public'
),
  pg_proc_with_proallargtypes_names AS (
    SELECT
      pg_proc_with_unnested_proallargtypes.oid,
      pg_proc_with_unnested_proallargtypes.proname,
      array_agg(pg_catalog.pg_type.typname) AS proallargtypes
    FROM pg_proc_with_unnested_proallargtypes
      LEFT JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = proallargtype
    GROUP BY
      pg_proc_with_unnested_proallargtypes.oid,
      pg_proc_with_unnested_proallargtypes.proname
  )
SELECT
  information_schema.routines.specific_name,
  information_schema.routines.routine_name,
  information_schema.routines.routine_schema,
  information_schema.routines.data_type,
  pg_proc_with_proallargtypes_names.proallargtypes
FROM information_schema.routines
    -- we can declare many function with the same name and schema as long as arg types are different
    -- This is the only right way to join pg_catalog.pg_proc and information_schema.routines, sadly
 JOIN pg_proc_with_proallargtypes_names 
    ON pg_proc_with_proallargtypes_names.proname || '_' ||  pg_proc_with_proallargtypes_names.oid = information_schema.routines.specific_name
;

欢迎任何重构:)

结果如下:

 specific_name  | routine_name | routine_schema | data_type |      proallargtypes      
----------------+--------------+----------------+-----------+--------------------------
 test_ret_16633 | test_ret     | public         | record    | {text,text,int4,varchar}
 test_ret_16635 | test_ret     | public         | integer   | {NULL}
(2 rows)

修改 输入和输出参数的识别并不简单,这是我对pg 9.2的解决方案

-- https://gist.github.com/subssn21/e9e121f6fd5ff50f688d
-- Allow us to use array_remove in pg < 9.3
CREATE OR REPLACE FUNCTION array_remove(a ANYARRAY, e ANYELEMENT)
  RETURNS ANYARRAY AS $$
BEGIN
  RETURN array(SELECT x FROM unnest(a) x WHERE x <> e);
END;
$$ LANGUAGE plpgsql;

-- edit: add support for function not returning tables, thx Tommaso Di Bucchianico
WITH pg_proc_with_unnested_proallargtypes AS (
    SELECT
      pg_catalog.pg_proc.oid,
      pg_catalog.pg_proc.proname,
      pg_catalog.pg_proc.proargmodes,
      CASE WHEN proallargtypes IS NOT NULL THEN unnest(proallargtypes) ELSE null END AS proallargtype
    FROM pg_catalog.pg_proc
      JOIN pg_catalog.pg_namespace ON pg_catalog.pg_proc.pronamespace = pg_catalog.pg_namespace.oid
    WHERE pg_catalog.pg_namespace.nspname = 'public'
),
pg_proc_with_unnested_proallargtypes_names_and_mode AS (
    SELECT
      pg_proc_with_unnested_proallargtypes.oid,
      pg_proc_with_unnested_proallargtypes.proname,
      pg_catalog.pg_type.typname,
      -- we can't unnest multiple array of same length the way we expect in pg 9.2
      -- just retrieve each mode manually using type row_number
      pg_proc_with_unnested_proallargtypes.proargmodes[row_number() OVER w] AS proargmode
    FROM pg_proc_with_unnested_proallargtypes
      LEFT JOIN pg_catalog.pg_type ON pg_catalog.pg_type.oid = proallargtype
    WINDOW w AS (PARTITION BY pg_proc_with_unnested_proallargtypes.proname)
),
pg_proc_with_input_and_output_type_names AS (
    SELECT
      pg_proc_with_unnested_proallargtypes_names_and_mode.oid,
      pg_proc_with_unnested_proallargtypes_names_and_mode.proname,
      array_agg(pg_proc_with_unnested_proallargtypes_names_and_mode.typname) AS proallargtypes,
      -- we should use FILTER, but that's not available in pg 9.2 :(
      array_remove(array_agg(
          -- see documentation for proargmodes here: http://www.postgresql.org/docs/9.2/static/catalog-pg-proc.html
          CASE WHEN pg_proc_with_unnested_proallargtypes_names_and_mode.proargmode = ANY(ARRAY['i', 'b', 'v'])
          THEN pg_proc_with_unnested_proallargtypes_names_and_mode.typname 
          ELSE NULL END
      ), NULL) AS proinputargtypes,
      array_remove(array_agg(
         -- see documentation for proargmodes here: http://www.postgresql.org/docs/9.2/static/catalog-pg-proc.html
         CASE WHEN pg_proc_with_unnested_proallargtypes_names_and_mode.proargmode = ANY(ARRAY['o', 'b', 't'])
         THEN pg_proc_with_unnested_proallargtypes_names_and_mode.typname
         ELSE NULL END
      ), NULL) AS prooutputargtypes
    FROM pg_proc_with_unnested_proallargtypes_names_and_mode
    GROUP BY
      pg_proc_with_unnested_proallargtypes_names_and_mode.oid,
      pg_proc_with_unnested_proallargtypes_names_and_mode.proname
)
SELECT 
  *
FROM pg_proc_with_input_and_output_type_names
;

这是我的示例输出:

  oid  |   proname    |      proallargtypes      | proinputargtypes | prooutputargtypes 
-------+--------------+--------------------------+------------------+-------------------
 16633 | test_ret     | {text,text,int4,varchar} | {text,text}      | {int4,varchar}
 16634 | array_remove | {NULL}                   | {}               | {}
 16635 | test_ret     | {NULL}                   | {}               | {}
(3 rows)

希望有所帮助:)