PostgREST 函数即 RPC:用 /rpc 端点把 PostgreSQL 函数暴露为 REST API 完整指南
2026/9/11 10:37:56 网站建设 项目流程

PostgREST 函数即 RPC:用 /rpc 端点把 PostgreSQL 函数暴露为 REST API 完整指南

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

导读

PostgREST 将 PostgreSQL 数据库函数直接映射为 HTTP 端点:只要函数位于暴露的 schema(默认public)中、且当前激活的数据库角色拥有执行权限,就可以通过/rpc/<function_name>统一前缀调用,支持POST传 JSON 参数、GET传查询字符串参数,还能对返回表类型的函数继续施加筛选、排序、分页与资源嵌套。本文以官方参考文档 docs/references/api/functions.rst 为主线,结合仓库内 CallPlan.hs、QueryBuilder.hs、Routine.hs 等源码,系统讲解 RPC 调用的全部形态:参数传递、JSON/XML/二进制原始体、数组与 variadic 参数、表值函数筛选与内联、标量返回值、未类型化函数与重载函数的边界条件。读完你将能够把任意复杂度的数据库逻辑(读、写、报错乃至 DDL)封装成可复用的 REST 资源。

为什么是/rpc前缀

PostgreSQL 允许表或视图与函数同名,例如同时存在CREATE TABLE users(...)CREATE FUNCTION users(...)。如果函数直接挂在根路径,PostgREST 就无法区分这次请求是命中表资源还是函数资源,产生路由冲突。为此 PostgREST 把所有函数调用统一收敛到/rpc前缀之下,从根本上消除了歧义。

函数能做的事情与 PostgreSQL 本身的能力一致,包括:

  • 读取数据(SELECT);
  • 修改数据(INSERT/UPDATE/DELETE,见后文update_data示例);
  • 抛出自定义错误(配合RAISE,错误会按 errors.rst 的规则映射为 HTTP 错误响应);
  • 甚至执行 DDL 操作。

需要注意一个明确的边界:存储过程(Stored Procedure,即CREATE PROCEDURE)不受支持,请使用CREATE FUNCTION。该限制在文档中以 warning 形式声明,仓库中 schema cache 也只查询函数(见 SchemaCache.hs 的decodeFuncs实现)。

从源码结构看,请求在 ApiRequest.hs 中把["rpc", pName]解析为ResourceRoutine pName,随后在 Plan.hs 的callPlan中决定以"键参数(KeyParams)"还是"单位置参数(OnePosParam)"方式生成调用计划,最终由 QueryBuilder.hs 的callPlanToQuery拼出实际的 SQL 调用。

重要:创建或修改函数后必须刷新 PostgREST 的 schema cache(参见 schema_reloading 相关说明),否则新函数不可见、签名变更不会生效。

用 POST 调用函数:JSON 对象即参数表

/rpc/<function>发送POST请求时,请求体是一个 JSON 对象,对象的每个键值对会映射为函数的一个参数(参数名即键名)。

先在数据库中创建如下函数:

CREATE FUNCTION add_them(a integer, b integer) RETURNS integer AS $$ SELECT a + b; $$ LANGUAGE SQL IMMUTABLE;

客户端调用:

curl "http://localhost:3000/rpc/add_them" \ -X POST -H "Content-Type: application/json" \ -d '{ "a": 1, "b": 2 }'

响应:

3

几点事实需要明确:

  • 标识符大小写:PostgreSQL 默认把所有未加引号的标识符转换为小写。如果函数名或参数名带大写字母,必须用双引号定义,例如CREATE FUNCTION "someFunc"("someParam" text) ...,调用时也需按原大小写传参。
  • POST 语义不限于"读"POST可以调用任何函数,包括带副作用的函数。而GET仅对不修改数据库的函数开放,判断依据是函数的 volatility(IMMUTABLE/STABLE允许 GET,VOLATILE不允许),详见 access_mode 相关配置。

用 GET 调用函数:查询字符串即参数表

若函数不修改数据库(volatility 为STABLEIMMUTABLE),它也会在GET方法下运行:

curl "http://localhost:3000/rpc/add_them?a=1&b=2"

GET场景下参数名与查询参数名一一对应:?a=1&b=2等价于 POST 的{ "a": 1, "b": 2 }

带默认值的参数可以省略。例如:

CREATE FUNCTION greet_user(username TEXT DEFAULT 'guest') RETURNS TEXT AS $$ SELECT 'Hello ' || username || '!'; $$ LANGUAGE SQL IMMUTABLE;
curl -i "http://localhost:3000/rpc/greet_user"

响应:

HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 "Hello guest!"

需要注意的是,省略参数仅对"定义了默认值"的参数成立。从 QueryBuilder.hs 的注释与实现看,未提供且无默认值的参数目前会被编译为NULL传入,因此请确保你的函数对缺省参数有 DEFAULT 声明,或者逻辑上容忍NULL

传 JSON 数组给函数(json / jsonb 参数)

如果需要一次性向函数传递多条数据(对象数组),可以定义一个参数类型为jsonjsonb的函数,客户端把整个数组嵌在对应参数名之下。这样可以在一次curl请求内完成原本需要多次请求的批量操作。

CREATE FUNCTION update_data(p_json jsonb) RETURNS void AS $$ DECLARE json_item json; BEGIN FOR json_item IN SELECT jsonb_array_elements(p_json) LOOP UPDATE data_table SET data_text_column = (json_item->>'data_text')::text WHERE data_int_column = (json_item->>'data_int')::integer; END LOOP; END; $$ LANGUAGE SQL IMMUTABLE;

注意上例中LANGUAGE SQLPL/pgSQL风格的DECLARE/BEGIN/END混用并不正确,实际生产中请为含DECLARE块的过程体使用LANGUAGE plpgsql;文档此处以演示批量更新思路为主。对应的 POST 请求:

curl "http://localhost:3000/rpc/update_data" \ -X POST -H "Content-Type: application/json" \ -d '{ "p_json": [ { "data_text": "one", "data_int": "1" }, { "data_text": "two", "data_int": "2" } ] }'

这个模式适合需要把多条业务记录在一次事务性调用中落库的场景,可以减少网络往返次数。

单个未命名 JSON 参数:请求体整体传给函数

如果想让整个 JSON 请求体作为唯一参数传给函数,可以定义一个有且仅有一个、且未命名json/jsonb参数。此时必须在请求中携带Content-Type: application/json

CREATE FUNCTION mult_them(json) RETURNS int AS $$ SELECT ($1->>'x')::int * ($1->>'y')::int $$ LANGUAGE SQL;
curl "http://localhost:3000/rpc/mult_them" \ -X POST -H "Content-Type: application/json" \ -d '{ "x": 4, "y": 2 }'

响应:

8

此处$1即整个 JSON 请求体。从源码看,这种"单未命名参数"形态对应 CallPlan.hs 中的OnePosParam(位置参数),并由 QueryBuilder.hs 的singleParameter分支生成调用。

一个重要的重载回退规则:如果存在一组重载函数,其中某个重载恰好只有一个未命名的json/jsonb参数,那么当 POST 请求中的参数无法匹配到任何其他重载时,PostgREST 会回退调用这个"单 JSON 参数"版本。这让单 JSON 参数函数成为"万能兜底"入口。

单个未命名参数传原始数据:bytea / text / xml

如果函数的唯一参数未命名,且类型是byteatextxml,POST 请求体就会以原始字节流形式整体作为该参数传入,而不再要求 JSON 包装。此时需要设置对应的Content-Type

参数类型必须的 Content-Type典型用途
xmltext/xml直接提交 XML 文档
byteaapplication/octet-stream上传二进制文件
texttext/plain提交纯文本

例如上传二进制文件到bytea字段:

CREATE TABLE files(blob bytea); CREATE FUNCTION upload_binary(bytea) RETURNS void AS $$ INSERT INTO files(blob) VALUES ($1); $$ LANGUAGE SQL;
curl "http://localhost:3000/rpc/upload_binary" \ -X POST -H "Content-Type: application/octet-stream" \ --data-binary "@file_name.ext"

响应:

HTTP/1.1 200 OK [ ... ]

--data-binary "@file_name.ext"会把本地文件的原始字节读入请求体,配合函数内的INSERT完成文件入库。同类思路也支持把 XML 文档、纯文本整段送入函数处理。

数组参数:POST 传 JSON 数组,GET 传数组字面量

PostgREST 支持直接调用带数组参数的函数。先定义一个数组参数函数:

create function plus_one(arr int[]) returns int[] as $$ SELECT array_agg(n + 1) FROM unnest($1) AS n; $$ language sql;

POST:参数值直接写 JSON 数组:

curl "http://localhost:3000/rpc/plus_one" \ -X POST -H "Content-Type: application/json" \ -d '{"arr": [1,2,3,4]}'

响应:

[2,3,4,5]

GET:使用 PostgreSQL 的数组字面量语法{1,2,3,4},注意花括号必须 URL 编码({%7B}%7D):

curl "http://localhost:3000/rpc/plus_one?arr=%7B1,2,3,4%7D"

PostgreSQL 10 之前的兼容性说明:在旧版本上,POST 载荷中传 PostgreSQL 原生数组需要先加引号再写数组字面量:

curl "http://localhost:3000/rpc/plus_one" \ -X POST -H "Content-Type: application/json" \ -d '{ "arr": "{1,2,3,4}" }'

这类旧版本上官方建议优先改用 JSON 参数类型来接收数组,避免字面量解析带来的兼容问题。

变参(VARIADIC)函数

VARIADIC函数允许在调用端以"不定数量实参"的形式传值。

create function plus_one(variadic v int[]) returns int[] as $$ SELECT array_agg(n + 1) FROM unnest($1) AS n; $$ language sql;
  • POST + JSON:把数组作为v参数传入:
curl "http://localhost:3000/rpc/plus_one" \ -X POST -H "Content-Type: application/json" \ -d '{"v": [1,2,3,4]}'

响应:

[2,3,4,5]
  • GET:重复同一个参数名,PostgREST 会将其合并为数组:
curl "http://localhost:3000/rpc/plus_one?v=1&v=2&v=3&v=4"
  • POST + form-urlencoded:同样重复参数名:
curl "http://localhost:3000/rpc/plus_one" \ -X POST -H "Content-Type: application/x-www-form-urlencoded" \ -d 'v=1&v=2&v=3&v=4'

源码层面,重复参数合并逻辑位于 CallPlan.hs:toRpcParams会先检测函数是否含 variadic 参数(pdHasVariadic),是则用mergeParams把同名参数累积成Variadic [v],否则直接转为Fixed v映射;最终在 QueryBuilder.hs 中为 variadic 参数拼出VARIADIC name := ...并以内建数组编码器编码。

表值函数(Table-Valued Functions)

返回表类型(RETURNS SETOF <table>RETURNS TABLE(...))的函数,返回结果可以当作表资源使用,支持与表、视图完全一致的读过滤器

  • 水平过滤(?col=eq.val/gt./like.等操作符);
  • 垂直过滤(?select=col1,col2);
  • 计数(Prefer: count=exact);
  • 分页(Range头或limit/offset参数);
  • 排序(order);
  • 资源嵌套:如果返回的表类型与其他表存在外键关系,还能用select=...:related(*)做资源嵌入。

例如:

CREATE FUNCTION best_films_2017() RETURNS SETOF films ..

带嵌入与字段选择的调用:

curl "http://localhost:3000/rpc/best_films_2017?select=title,director:directors(*)"

带过滤、排序的调用:

curl "http://localhost:3000/rpc/best_films_2017?rating=gt.8&order=title.desc"

从实现看,表值函数返回类型在 Routine.hs 中被建模为SetOf (Composite qi)funcTableName(Routine.hs)把复合类型还原为对应表名,从而让 PostgREST 复用整套表查询的 read plan 管线(ReadPlan.hs 中的selectwhere_orderrange_等字段)。

函数内联(Function Inlining)

当函数满足 PostgreSQL 的 SQL 函数内联规则(LANGUAGE SQLSTABLE、函数体是简单 SELECT 等条件,见 PostgreSQL Wiki 的 Inlining conditions for table functions)时,PostgREST 施加的筛选、排序和 limit 会被下推进函数体内部执行,而不是先物化函数结果再过滤——这是表值函数查询性能的关键。

例如:

create function getallprojects() returns setof projects language sql stable as $$ select * from projects; $$;

Accept: application/vnd.pgrst.plan头获取执行计划(explain_plan相关说明见 PlanSpec 测试):

curl "http://localhost:3000/rpc/getallprojects?id=eq.1" \ -H "Accept: application/vnd.pgrst.plan"

执行计划:

Aggregate (cost=8.18..8.20 rows=1 width=112) -> Index Scan using projects_pkey on projects (cost=0.15..8.17 rows=1 width=40) Index Cond: (id = 1)

计划里没有 "Function Scan" 节点,说明函数已被内联,WHERE id = 1直接下推到projects表的主键索引扫描——这正是 SQL 函数优于普通 PL/pgSQL 函数的重要理由:它让数据库优化器看到函数内部的真实表结构。

表值函数的水平过滤

表值函数支持对已选择与未选择列进行水平过滤。下面的调用在select中只返回id, client_id,却用未选择的name列做过滤:

curl "http://localhost:3000/rpc/getallprojects?select=id,client_id&name=like.OSX"
[ { "id": 4, "client_id": 2 } ]

实现上,Plan.hs 的getFilterFieldNames会从 read plan 中提取所有过滤列,QueryBuilder.hs 在拼 SELECT 列时把filterFieldsreturnings合并去重,确保过滤列即使不在 select 中也会被查询出来供 WHERE 使用。

标量函数:返回格式自动识别

PostgREST 会依据 schema cache 中记录的返回类型自动区分"标量函数"和"表值函数",并据此决定响应形态:

  • 标量函数(RETURNS int等):直接返回裸 JSON 值(数字、字符串、布尔);
curl "http://localhost:3000/rpc/add_them?a=1&b=2"
3
  • 表值函数:返回 JSON 数组:
curl "http://localhost:3000/rpc/best_films_2017"
[ { "title": "Okja", "rating": 7.4}, { "title": "Call me by your name", "rating": 8}, { "title": "Blade Runner 2049", "rating": 8.1} ]

类型判定逻辑集中在 Routine.hs:funcReturnsScalarSingle (Scalar _))、funcReturnsSetOfScalarSetOf (Scalar _))、funcReturnsSingleCompositefuncReturnsVoid分别处理单值、标量集合、单条复合记录、void等情形;QueryBuilder.hs 在拼 SQL 时若判定为标量则取pgrst_call.pgrst_scalar一列。如需手动控制返回格式(如强制二进制),参见 media_type_handlers.rst。

未类型化函数(record / SETOF record)

返回recordSETOF record的函数也可调用,适合快速原型验证:

create function projects_setof_record() returns setof record as $$ select * from projects; $$ language sql;
curl "http://localhost:3000/rpc/projects_setof_record"
[{"id":1,"name":"Windows 7","client_id":1}, {"id":2,"name":"Windows 10","client_id":1}, {"id":3,"name":"IOS","client_id":2}]

但要注意边界:record返回类型无法使用垂直过滤(select)与水平过滤(WHERE),因为 PostgREST 不知道记录里有哪些列。因此官方建议仅用于快速测试,生产函数应始终声明明确的返回类型(具体表类型或TABLE(...)结构),这样才能获得筛选、排序、分页与嵌入能力。

重载函数

PostgreSQL 允许同名函数按参数个数/类型重载,PostgREST 支持按"本次请求提供的参数"自动选择重载版本:

CREATE FUNCTION rental_duration(customer_id integer) .. CREATE FUNCTION rental_duration(customer_id integer, from_date date) ..

只传必填参数:

curl "http://localhost:3000/rpc/rental_duration?customer_id=232"

补充可选参数(命中第二版重载):

curl "http://localhost:3000/rpc/rental_duration?customer_id=232&from_date=2018-07-01"

实现上,schema cache 用RoutineMap = HashMap QualifiedIdentifier [Routine]保存每个函数名的全部重载(Routine.hs),并按参数个数升序排序(Routine.hs 的Ord Routine实例),以支持"最少参数优先匹配"的查找策略。

一个必须避开的限制:参数名相同但类型不同的重载不受支持(例如func(a int)func(a text)同时存在),因为参数名相同意味着无法通过请求键区分该选哪个版本,匹配会变得不明确。

源码视角:一次 RPC 调用的完整链路

把以上行为串联起来,一次/rpc请求在仓库中的处理路径大致是:

  1. 路由识别ApiRequest.hs["rpc", pName]解析为ResourceRoutine pName(ApiRequest.hs)。
  2. 参数规整toRpcParams(CallPlan.hs)把查询参数/表单参数转换为HashMap Text RpcParamValue,variadic 参数在此合并;JSON 请求体则保持为JsonArgs (Maybe LBS.ByteString)
  3. 生成调用计划callPlan(Plan.hs)依据pdParams判断是KeyParams(具名参数name := value)还是OnePosParam(唯一未命名参数,位置传参)。
  4. 拼装 SQLcallPlanToQuery(QueryBuilder.hs)把参数按name := 'value'::type形式编码(variadic 前缀VARIADIC),标量/标量集合用子查询包一层pgrst_scalar,表值函数则作为FROM pgrst_call的表源继续接 read plan 的 WHERE/ORDER/LIMIT/SELECT。
  5. 执行与格式化:最终由 Statements.hs 的mainCall组装语句,Query.hs 统一执行,响应按返回类型序列化为 JSON。

以上每一环都有对应的测试佐证,例如test/spec/Feature/Query/RpcSpec.hs覆盖了 RPC 的 limit/offset(RpcSpec.hs)、可限制过程结果(RpcSpec.hs)以及 bit/char 类型参数(RpcSpec.hs)等场景。

实战要点速查

场景方法关键要求
普通多参数调用POST JSON 对象 或 GET 查询串参数名与函数参数一致
批量写多行POST,参数为json/jsonb,值传对象数组参数名包裹整个数组
请求体整体作为参数POST,唯一未命名json/jsonb参数Content-Type: application/json;可作重载兜底
传二进制文件POST,唯一未命名bytea参数Content-Type: application/octet-stream
传 XML / 纯文本POST,唯一未命名xml/text参数Content-Type: text/xml/text/plain
数组参数POST JSON 数组;GET 用 URL 编码的数组字面量花括号需编码为%7B%7D
变参函数POST 数组 / GET 重复参数名 / form 重复参数名参数需声明为VARIADIC
表值函数复用表的筛选、排序、分页、嵌入返回类型必须是具体表类型
标量函数响应为裸 JSON 值返回类型为标量
未类型化函数可调用但无过滤能力仅限快速测试
重载函数按提供的参数个数自动选版本不允许"同名不同类型"重载

最后再提醒一遍:任何函数定义或签名变更后,请先刷新 PostgREST 的 schema cache 再验证;生产环境建议为所有函数声明严格的返回类型,把数据库逻辑全部收敛到/rpc这一条 REST 边界之内。

【免费下载链接】postgrestREST API for any Postgres database项目地址: https://gitcode.com/GitHub_Trending/po/postgrest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询