资讯详情

资讯详情

PostgREST Domain Representations 完整指南:用 PostgreSQL Domain 与 Cast 分离数据存储与 API 呈现格式

PostgREST Domain Representations 完整指南用 PostgreSQL Domain 与 Cast 分离数据存储与 API 呈现格式【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrestDomain Representations 是 PostgREST 提供的一项核心能力它把数据如何呈现API 层与数据如何存储数据库层彻底解耦通过 PostgreSQL 的DOMAIN与CAST组合你可以让同一个 UUID 主键在数据库里保持标准格式却在 JSON 响应、URL 过滤参数和请求体里自动以base64、base58等缩短格式出现且全程零应用层代码。读完本文你将掌握自定义 Domain、为 JSON 响应 / 查询过滤 / 请求体三条数据通路各配置一个 Cast 的完整操作并理解其底层实现原理及与视图方案相比的优势边界。核心思想呈现与存储的分离Domain Representations 的基本思路是使用 PostgreSQL 的 domains 定义业务语义化的数据类型再借助 casts 让这些 Domain 在不同数据通道上自动完成格式转换。简单说Domain 负责存储语义Cast 负责呈现格式PostgREST 在规划查询时发现可用的 Cast就会在生成 SQL 时自动包裹转换函数。从源码层面看这一机制落在地图构建与查询规划两处Cast 地图构建PostgREST 启动时执行一次系统目录查询把所有通往/来自 Domain 的隐式函数型 Cast加载进内存。见 SchemaCache.hs 中的 dataRepresentations 查询其筛选条件是必须是隐式 Castc.castcontext i必须是函数型 Castc.castmethod f即WITH FUNCTION形式调用者对该函数拥有execute权限has_function_privilege(c.castfunc, execute)转换的某一端必须是 Domainsrc_t.typtype d或dst_t.typtype d且另一端限定为json或text两种中间类型。查询规划期应用在 Plan.hs 的 ResolverContext 中携带representations :: RepresentationsMap与outputType通过withOutputFormatDomain→输出类型、withTextParsetext→Domain、withJsonParsejson→Domain三个方向查找并挂载转换函数见 withTransformer 等实现。也就是说PostgREST 把 URL 查询串中的值一律视为text把请求体/响应体视为json于是三个方向的 Cast 恰好覆盖了 API 的全部数据通路。第一步创建一个自定义 Domain假设你的主键使用uuid类型但希望展示给 Web 用户的是缩短后的字符串。首先基于uuid创建 Domain并把表的主键类型改为该 Domaincreate 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);app_uuid直接继承uuid的全部约束与行为所以它在数据库内部仍然是标准 UUID 语义排序、索引、外键引用均不受影响。第二步Domain 响应格式Domain → JSON我们希望响应中的id以base64编码缩短。由于 PostgREST 的响应体走 JSON 通道只需提供一个把app_uuid转成json的函数再注册为隐式 Cast 即可函数名是任意的-- 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 自身会忽略定义在 Domain 上的 Cast其解释权完全留给应用PostgREST 正是这个应用。社区正在 pgsql-hackers 邮件列表 讨论是否把该行为并入 PostgreSQL 内核。示例使用base64只是为了利用 PostgreSQL 内建函数简化演示对 URL 更友好的做法是base58编码无、/、等需要转义的字符。从测试 fixture 也能看到该方向的多种实际应用例如 test/spec/fixtures/schema.sql 中的titlecasetext、color、isodate、bytea_b64、unixtz、monetary等 Domain 均定义了Domain AS json的 Cast。重要创建 Cast 之后必须刷新 PostgREST 的 schema cache 才会生效刷新方式见 schema cache 刷新机制。这是因为 Cast 地图是在启动/重载时一次性从pg_cast系统目录抓取的。实现原理转换函数如何被挂到 SQL 上在查询规划阶段PostgREST 会把字段的名义类型colNominalType即 Domain 名与输出类型json作为键去representations地图中查找转换函数找到后写入字段的cfTransform最终拼进生成的 SQL。相关逻辑见 Plan.hs 的 expandStarsForTable 与 hasOutputRep。若目标列是数组等复合场景转换函数会通过unnest()对数组逐元素应用避免生成长到不可读的巨型 SQL详见 SqlFragment.hs 中的说明。另外 Plan/Types.hs 中 CoercibleField 的 cfBaseType 保存了 Domain 的基底类型如uuid用于保证to_jsonb(col)等内置转换仍以基底类型语义执行。第三步Domain 过滤格式text → DomainURL 查询串在 PostgREST 眼里最通用的类型就是text因此要让?ideq.ZLOTeeNEY4pkp9OxA这种缩短格式的过滤生效需要反向提供一个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?ideq.ZLOTeeNEY4pkp9OxA \ -H Accept: application/json[{id:hGxP/ZLOTeeNEY4pkp9OxA,name:John Doe}]注意如果没有定义text→app_uuid的 Cast过滤依然可用原生 UUID 格式846c4ffd-92ce-4de7-8d11-8e29929f4ec4正常工作。也就是说Cast 存在与否只是是否支持额外格式的区别原生格式始终可用。这一方向在源码中对应 Plan.hs 的 withTextParse它把过滤器OpExpr中出现的值按text→ 字段名义类型查找转换函数配合 resolveQueryInputField 应用于过滤条件。测试侧可以参考 QuerySpec.hs 中针对 tsvector Domain 的过滤器测试它验证了fts(simple).of全文检索过滤在列类型为 Domain 时依然生效。第四步Domain 请求体格式json → Domain创建新记录或更新记录时请求体是 JSON要接受缩短格式的id需要再定义一个json→app_uuid的转换。这里可以复用上一步的app_uuid(text)函数-- 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: returnrepresentation \ -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。该方向在源码中对应 Plan.hs 的 withJsonParse转换函数会包裹到 INSERT/UPDATE 语句的对应值上。仓库测试 fixture 提供了完整的双/三向 Cast 范例例如color、bytea_b64、unixtz、monetary均同时定义了 Domain→json、json→Domain 和 text→Domain 三个方向见 test/spec/fixtures/schema.sql。三个方向的统一视图把上面三步放在一起就构成了一张完整的转换矩阵数据通路方向Cast说明响应体app_uuid→jsonCREATE CAST (app_uuid AS json) ...输出时自动缩短查询过滤text→app_uuidCREATE CAST (text AS app_uuid) ...URL 查询串被视为 text请求体json→app_uuidCREATE CAST (json AS app_uuid) ...输入时自动还原三者相互独立你可以只配置响应方向只读场景、只配置输入方向写多读少场景也可以全部配置。无论配置多少原生格式始终作为保底可用。PostgREST 对 Cast 的发现规则也印证了这一点——只有隐式 函数型 有执行权限 一端是 Domain、另一端是 json/text的 Cast 才会被加载见 SchemaCache.hs。相比视图Views的优势视图同样能改变底层类型的呈现格式但会引入一系列复杂度这正是 Domain Representations 想要规避的不可更新在视图中格式化列会使其变成不可更新视图non-updatable因为 PostgreSQL 不知道如何逆转该变换要可写只能用INSTEAD OF触发器兜底。过滤时全表扫描对该格式化列做过滤时PostgreSQL 无法下推条件退化为全表扫描这个问题同样存在于计算字段场景。性能损失只能靠计算索引或物化生成列materialized generated column缓解。破坏外键关系检测若格式化列被用作外键PostgREST 将无法再识别该关系资源嵌入随之失效只能通过计算关系曲线救国。Domain Representations 可以同时规避以上三个问题存储层始终是原生类型所以可更新、可用索引、可被外键检测识别。它唯一的代价是已有表需要修改列类型。不过这通常是一次很快的操作——Domain 与其底层类型是二进制兼容binary coercible的不会触发表重写table rewrite。为什么不直接创建基础类型Base Type有人会问为什么不干脆CREATE TYPE app_uuid (INTERNALLENGTH 22, INPUT app_uuid_parser, OUTPUT app_uuid_formatter)创建自定义基础类型原因有二创建基础类型需要超级用户权限这在云托管数据库上通常不可用更重要的是基础类型方案会让数据如何呈现反过来决定数据如何存储方向反了。Domain Cast 方案保持存储语义独立于呈现格式职责划分更干净。实践要点与注意事项函数需为 IMMUTABLE所有转换函数都应声明IMMUTABLE这样 PostgreSQL 才能安全地将其用于索引和查询优化PostgREST 生成 SQL 时的行为也最可预期。函数权限PostgREST 加载 Cast 时会检查has_function_privilege(c.castfunc, execute)见 dataRepresentations 查询因此请确保 PostgREST 连接角色对转换函数拥有执行权限。schema 隔离Domain 也可以跨 schema 使用测试覆盖了位于非暴露 schema 的 Domain 作为处理器参数位于暴露 schema 的 Domain等场景见 MultipleSchemaSpec.hs。示例中请求体方向的 Cast 显式写作public.app_uuid正是为了在跨 schema 场景下消除歧义。刷新 schema cache新增或修改 Cast 后务必按 schema cache 刷新机制 触发重载例如通过NOTIFY pgrst、重载信号或自动重载否则新 Cast 不会立即生效。原生格式保底Cast 只是额外的格式能力原生 UUID、原生过滤、原生请求体格式在任何时候都可用这为灰度迁移提供了天然的平滑路径。过滤与排序的配合过滤器经过 Cast 还原为原生类型后PostgreSQL 仍能使用底层类型的索引这是相对视图方案最直接的性能收益。相关过滤器语法见 tables_views.rst 的过滤章节插入/更新的具体用法见 插入与更新。综上Domain Representations 把存储格式与呈现格式的职责彻底分离是 PostgREST 中处理 UUID 缩短、时间戳格式化、枚举别名、数值单位换算等场景的推荐做法其实现Cast 地图 三向转换挂载在 SchemaCache.hs 与 Plan.hs 中都有清晰可读的源码支撑配合 测试 fixture 中的多种 Domain 范例可以直接照搬到自己的项目中。【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →