域表示法#
域表示法将“如何表示数据”与“如何存储数据”分开。它的工作原理是创建 domains 和 casts ,后者作用于前者,以不同的格式呈现和接收数据。
自定义域#
假设您想要使用 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');
域响应格式#
我们可以缩短 uuid
使用 base64
编码。让我们使用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"}]
备注
域上的强制转换被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
定义后,过滤器仍将使用本机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
定义后,请求正文仍将使用本机UUID格式 (cc7ec76c-5254-4dfc-bf19-9a70ba2ae9b9
)。
相对于视图的优势#
Views 还允许我们更改基础类型的格式。然而,它们也伴随着增加复杂性的缺点。
在视图中设置列的格式使其成为 non-updatable 因为波斯格雷斯不知道如何逆转这种转变。这可以使用INSTEAD OF触发器来解决。
当按此列进行过滤时,出于相同的原因,我们会获得完整的表扫描(也适用于 计算字段 )。这里的性能损失可以通过计算索引或使用物化生成的列来避免。
如果格式化的列用作外键,则PostgREST无法再检测到该关系,并且 资源嵌入 休息一下。这可以通过以下方式解决 计算出的关系 。
域表示避免了所有上述缺点。它们唯一的缺点是对于现有的表,您必须更改列类型。但这应该是一个快速的操作,因为域对其底层类型是二进制强制的。将不需要重写表。
备注
为什么不创建一个 base type 取而代之的是? CREATE TYPE app_uuid (INTERNALLENGTH = 22, INPUT = app_uuid_parser, OUTPUT = app_uuid_formatter)
。
创建基本类型需要超级用户,而这在云托管数据库中是受限的。此外,这种方式让“数据是如何呈现的”决定了“数据是如何存储的”,这将是反向的。