媒体类型处理程序#

媒体类型处理程序允许PostgREST提供自定义媒体类型。这些处理程序扩展了 builtin ones 并且还可以覆盖它们。

媒体类型使用类型别名表示 domains 他们的名字必须符合 RFC 6838 requirements

CREATE DOMAIN "application/json" AS json;

使用这些域, functions 可以成为操纵者和 user-defined aggregates 可以充当的处理程序 表和视图表值函数

重要

  • PostgREST供应商介质类型 (application/vnd.pgrst.planapplication/vnd.pgrst.objectapplication/vnd.pgrst.array )不能被覆盖。

  • 长媒体类型,如 application/vnd.openxmlformats-officedocument.wordprocessingml.document 无法表示为属性域,因为它们超过 PostgreSQL identifier length 。对于这些,您可以使用 “任何”处理程序

处理程序函数#

作为一个示例,让我们获得 TWKB PostGIS几何图形的压缩二进制格式。

create extension postgis;

create table lines (
  id   int primary key
, name text
, geom geometry(LINESTRING, 4326)
);

insert into lines values (1, 'line-1', 'LINESTRING(1 1,5 5)'::geometry), (2, 'line-2', 'LINESTRING(2 2,6 6)'::geometry);

为此,您可以创建供应商媒体类型。

create domain "application/vnd.twkb" as bytea;

并将其用作函数的返回类型,以使其成为处理程序。

create or replace function get_line (id int)
returns "application/vnd.twkb" as $$
  select st_astwkb(geom) from lines where id = get_line.id;
$$ language sql;

备注

对于PostgreSQL<=12,您需要对函数体进行强制转换 st_astwkb(geom)::"application/vnd.twkb"

现在您可以请求 TWKB 输出如下:

curl 'localhost:3000/rpc/get_line?id=1' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

请注意,PostgREST将自动设置 Content-Typeapplication/vnd.twkb

表/视图的处理程序#

要从压缩格式中获益,如 TWKB ,则获取多行比获取一行更有意义。让我们通过为表添加一个处理程序来实现这一点。

通过使用域媒体类型作为其转换或最终函数的返回类型,可以将用户定义的聚合转换为处理程序。

让我们为这个例子创建一个转移函数。

create or replace function twkb_handler_transition (state bytea, next lines)
returns "application/vnd.twkb" as $$
  select state || st_astwkb(next.geom);
$$ language sql;

现在,我们将在为 lines 桌子。

create or replace aggregate twkb_agg (lines) (
  initcond = ''
, stype = "application/vnd.twkb"
, sfunc = twkb_handler_transition
);

备注

您可以通过测试看到此聚合与以下各项一起工作:

SELECT twkb_agg(l) from lines l;

                           twkb_agg
---------------------------------------------------------------
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
(1 row)

现在,您可以使用 twkb 媒体类型:

curl 'localhost:3000/lines' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

如果有一个表值函数返回相同的表类型,则处理程序也可以对其执行操作。

create or replace function get_lines ()
returns setof lines as $$
  select * from lines;
$$ language sql;
curl 'localhost:3000/get_lines' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

重写内置处理程序#

让我们重写现有的 text/csv 表的处理程序,以提供更复杂的CSV输出。它将包括一个 Byte order mark (BOM) 外加一个 Content-Disposition 标头以设置下载文件的名称。

为标准创建属性域 text/csv 媒体类型。

create domain "text/csv" as text;

以及返回域的转换函数。

create or replace function bom_csv_trans (state text, next lines)
returns "text/csv" as $$
  select state || next.id::text || ',' || next.name || ',' || next.geom::text || E'\n';
$$ language sql;

这一次,我们将添加最后一个函数。这将添加CSV标头、BOM和 Content-Disposition 标题。

create or replace function bom_csv_final (data "text/csv")
returns "text/csv" as $$
  -- set the Content-Disposition header
  select set_config('response.headers', '[{"Content-Disposition": "attachment; filename=\"lines.csv\""}]', true);
  select
    -- EFBBBF is the BOM in UTF8 https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
    convert_from (decode (E'EFBBBF', 'hex'),'UTF8') ||
    -- the header for the CSV
    (E'id,name,geom\n' || data);
$$ language sql;

现在,将转换和最终函数用作新聚合的一部分。

create or replace aggregate bom_csv_agg (lines) (
  initcond = ''
, stype = "text/csv"
, sfunc = bom_csv_trans
, finalfunc = bom_csv_final
);

备注

您可以使用以下命令进行测试:

select bom_csv_agg(l) from lines l;
                                             bom_csv_agg
-----------------------------------------------------------------------------------------------------
 id,name,geom                                                                                      +
 1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
 2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+

(1 row)

并按如下方式请求:

curl 'localhost:3000/lines' -i \
  -H "Accept: text/csv"

HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="lines.csv"

id,name,geom
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840

“任何”处理程序#

为了获得更大的灵活性,还可以使用名为的域来定义一个通用处理程序 */* (任何媒体类型)。此处理程序遵循以下规则:

  • 它响应所有媒体类型,甚至响应不包含 Accept 标题。

  • 它设置了 Content-Type 标题收件人 application/octet-stream 默认情况下,但可以在函数内部使用 响应标头

  • 它会覆盖所有其他处理程序 (builtin 或定制),所以最好是为独立的函数或视图执行此操作。

让我们为一个视图定义一个Any处理程序,该处理程序总是以 XML 输出。它会接受 text/xmlapplication/xml*/* 并拒绝其他媒体类型。

create domain "*/*" as bytea;

-- we'll use an .xml suffix for the view to be clear its output is always XML
create view "lines.xml" as
select * from lines;

-- transition function
create or replace function lines_xml_trans (state "*/*", next "lines.xml")
returns "*/*" as $$
  select state || xmlelement(name line, xmlattributes(next.id as id, next.name as name), next.geom)::text::bytea || E'\n' ;
$$ language sql;

-- final function
create or replace function lines_xml_final (data "*/*")
returns "*/*" as $$
declare
  -- get the Accept header
  req_accept text := current_setting('request.headers', true)::json->>'accept';
begin
  -- when we need to override the default Content-Type (application/octet-stream) set by PostgREST
  if req_accept = '*/*' then
    perform set_config('response.headers', json_build_array(json_build_object('Content-Type', 'text/xml'))::text, true);
  elsif req_accept IN ('application/xml', 'text/xml') then
    perform set_config('response.headers', json_build_array(json_build_object('Content-Type', req_accept))::text, true);
  else
    -- we'll reject other non XML media types, we need to reject manually since */* will command PostgREST to accept all media types
    raise sqlstate 'PT415' using message = 'Unsupported Media Type';
  end if;

  return data;
end; $$ language plpgsql;

-- new aggregate
create or replace aggregate lines_xml_agg ("lines.xml") (
  stype = "*/*"
, sfunc = lines_xml_trans
, finalfunc = lines_xml_final
);

在SQL上进行测试:

select (encode(lines_xml_agg(x), 'escape'))::xml from "lines.xml" x;
                                                            encode
------------------------------------------------------------------------------------------------------------------------------
 <line id="1" name="line-1">0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440</line>+
 <line id="2" name="line-2">0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840</line>+

现在我们可以省略 Accept 标头,它将使用XML进行响应。

curl 'localhost:3000/lines.xml' -i

HTTP/1.1 200 OK
Content-Type: text/xml

<line id="1" name="line-1">0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440</line>
<line id="2" name="line-2">0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840</line>

并且它将只接受XML媒体类型。

curl 'localhost:3000/lines.xml' -i \
  -H "Accept: text/xml"

HTTP/1.1 200 OK
Content-Type: text/xml
curl 'localhost:3000/lines.xml' -i  \
  -H "Accept: application/xml"

HTTP/1.1 200 OK
Content-Type: text/xml
curl 'localhost:3000/lines.xml' -i \
  -H "Accept: unknown/media"

HTTP/1.1 415 Unsupported Media Type