域表示
域表示将“数据呈现方式”与“数据存储方式”分离。它通过创建 域 和 转换 来实现,后者作用于前者以不同格式呈现和接收数据。
自定义域
假设您想使用 uuid
类型作为主键,并希望将其缩短以供 Web 用户查看。
为此,让我们创建一个基于 uuid
的域。
create domain app_uuid as uuid;
-- and use it as our table PK.
create table profiles(
id app_uuid
, name text
);
-- some data for the example
insert into profiles values ('846c4ffd-92ce-4de7-8d11-8e29929f4ec4', 'John Doe');
域响应格式
我们可以使用 base64
编码来缩短 uuid
。在本例中,让我们使用 JSON 作为我们的响应格式。
要更改 JSON 的域格式,请创建一个函数将 app_uuid
转换为 json
。
-- the name of the function is arbitrary
CREATE OR REPLACE FUNCTION json(app_uuid) RETURNS json AS $$
select to_json(encode(uuid_send($1),'base64'));
$$ LANGUAGE SQL IMMUTABLE;
-- check it works
select json('846c4ffd-92ce-4de7-8d11-8e29929f4ec4'::app_uuid);
json
----------------------------
"hGxP/ZLOTeeNEY4pkp9OxA=="
然后创建一个 CAST,告诉 PostgREST 在每次请求 JSON 响应时自动转换它。
CREATE CAST (app_uuid AS json) WITH FUNCTION json(app_uuid) AS IMPLICIT;
这样您就可以以缩短的格式获取数据。
curl "http://localhost:3000/profiles" \
-H "Accept: application/json"
[{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
注意
域上的 CAST 会被 PostgreSQL 忽略,其解释留给应用程序。我们正在讨论在 pgsql-hackers 上包含域表示行为的可能性。
使用
base58
编码更有意义,因为它对 URL 友好,但为了简单起见,我们使用base64
(PostgreSQL 本地支持)。
重要
在域上创建 CAST 后,您必须刷新 PostgREST 架构缓存。请参阅 架构缓存重新加载。
域过滤器格式
为了使 水平过滤 与缩短的格式一起使用,您需要不同的转换。
PostgREST 将 URL 查询字符串视为最通用的 text
。因此,让我们创建一个函数将 text
转换为 app_uuid
。
-- the name of the function is arbitrary
CREATE OR REPLACE FUNCTION app_uuid(text) RETURNS app_uuid AS $$
select substring(decode($1,'base64')::text from 3)::uuid;
$$ LANGUAGE SQL IMMUTABLE;
-- plus a CAST to tell PostgREST to use this function
CREATE CAST (text AS app_uuid) WITH FUNCTION app_uuid(text) AS IMPLICIT;
现在您可以像往常一样过滤。
curl "http://localhost:3000/profiles?id=eq.ZLOTeeNEY4pkp9OxA==" \
-H "Accept: application/json"
[{"id":"hGxP/ZLOTeeNEY4pkp9OxA==","name":"John Doe"}]
注意
如果没有从 text
到 app_uuid
的 CAST 定义,过滤器将仍然使用本机 uuid 格式 (846c4ffd-92ce-4de7-8d11-8e29929f4ec4
) 工作。
域请求主体格式
要接受 JSON 请求主体中的缩短格式(例如,在创建新记录时),请定义一个 json
到 app_uuid
的转换。
-- the name of the function is arbitrary
CREATE OR REPLACE FUNCTION app_uuid(json) RETURNS public.app_uuid AS $$
-- here we reuse the previous app_uuid(text) function
select app_uuid($1 #>> '{}');
$$ LANGUAGE SQL IMMUTABLE;
CREATE CAST (json AS public.app_uuid) WITH FUNCTION app_uuid(json) AS IMPLICIT;
curl "http://localhost:3000/profiles" \
-H "Prefer: return=representation" \
-H "Content-Type: application/json" \
-d @- <<JSON
{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}
JSON
响应
[{"id":"zH7HbFJUTfy/GZpwuirpuQ==","name":"Jane Doe"}]
请注意,在数据库端,我们使用的是常规的 uuid
格式。
select * from profiles;
id | name
--------------------------------------+----------
846c4ffd-92ce-4de7-8d11-8e29929f4ec4 | John Doe
cc7ec76c-5254-4dfc-bf19-9a70ba2ae9b9 | Jane Doe
(2 rows)
注意
如果没有定义从 json
到 app_uuid
的 CAST,请求体仍然可以使用原生 uuid 格式(cc7ec76c-5254-4dfc-bf19-9a70ba2ae9b9
)。
相对于视图的优势
视图 也允许我们更改底层类型的格式。但是它们也有一些缺点,会增加复杂性。
在视图中格式化列会使其 不可更新,因为 Postgres 不知道如何反转转换。可以使用 INSTEAD OF 触发器来解决这个问题。
当按此列进行过滤时,由于相同的原因,我们会得到全表扫描(也适用于 计算字段)。可以使用计算索引或使用物化生成列来避免这里的性能损失。
如果格式化后的列用作外键,PostgREST 将无法再检测到该关系,并且 资源嵌入 会中断。可以使用 计算关系 来解决这个问题。
域表示避免了以上所有缺点。它们唯一的缺点是,对于现有表,您必须更改列类型。但这应该是一个快速的操作,因为域与它们的底层类型是二进制可强制转换的。不需要重写表。
注意
为什么不创建一个 基本类型 呢? CREATE TYPE app_uuid (INTERNALLENGTH = 22, INPUT = app_uuid_parser, OUTPUT = app_uuid_formatter)
。
创建基本类型需要超级用户权限,而云托管数据库中限制了超级用户权限。此外,这种方式让“数据呈现方式”决定“数据存储方式”,这将是倒退的。