事务

用户模拟 之后,对 API 资源 的每个请求都在事务中运行。事务的顺序如下

START TRANSACTION; -- <Access Mode> <Isolation Level>
-- <Transaction-scoped settings>
-- <Main Query>
END; -- <Transaction End>

访问模式

访问模式决定事务是否可以修改数据库。有两种可能的值:只读和读写。

在只读事务中修改数据库是不可能的。PostgREST 利用这一点来在 GET 和 HEAD 请求中强制执行 HTTP 语义。考虑以下情况

CREATE SEQUENCE callcounter_count START 1;

CREATE VIEW callcounter AS
SELECT nextval('callcounter_count');

由于 callcounter 视图修改了序列,因此使用 GET 或 HEAD 调用它会导致错误

curl "http://localhost:3000/callcounter"
HTTP/1.1 405 Method Not Allowed

{"code":"25006","details":null,"hint":null,"message":"cannot execute nextval() in a read-only transaction"}

表和视图上的访问模式

表和视图的访问模式由 HTTP 方法决定。

HTTP 方法

访问模式

GET, HEAD

只读

POST, PATCH, PUT, DELETE

读写

函数的访问模式

函数作为 RPC 还取决于函数的易变性

访问模式

HTTP 方法

易变

稳定

不可变

GET, HEAD

只读

只读

只读

POST

读写

只读

只读

注意

  • 易变性标记是对函数行为的承诺。PostgreSQL 允许您将修改数据库的函数标记为 IMMUTABLESTABLE,而不会出现错误。但是,由于函数处于只读事务中,因此它将在 PostgREST 下失败。

  • OPTIONS 方法 不会启动事务,因此这里不相关。

隔离级别

每个事务都使用 PostgreSQL 的默认隔离级别:READ COMMITTED。除非您修改了模拟角色或函数的default_transaction_isolation

ALTER ROLE webuser SET default_transaction_isolation TO 'repeatable read';

每个 webuser 的查询都将使用设置为 REPEATABLE READ 的 default_transaction_isolation 执行。

或者更改每个函数调用的隔离级别。

CREATE OR REPLACE FUNCTION myfunc()
RETURNS text as $$
  SELECT 'hello';
$$
LANGUAGE SQL
SET default_transaction_isolation TO 'serializable';

事务范围设置

PostgREST 使用与事务生命周期绑定的设置。这些设置可用于获取有关 HTTP 请求的信息。或者修改 HTTP 响应。

您可以使用 current_setting 获取这些设置。

-- request settings use the ``request.`` prefix.
SELECT
  current_setting('request.<setting>', true);

您可以使用 set_config 设置这些设置。

-- response settings use the ``response.`` prefix.
SELECT
  set_config('response.<setting>', 'value1' ,true);

请求头、Cookie 和 JWT 声明

PostgREST 将头、Cookie 和头存储为 JSON。要获取它们

-- To get all the headers sent in the request
SELECT current_setting('request.headers', true)::json;

-- To get a single header, you can use JSON arrow operators
SELECT current_setting('request.headers', true)::json->>'user-agent';

-- value of sessionId in a cookie
SELECT current_setting('request.cookies', true)::json->>'sessionId';

-- value of the email claim in a jwt
SELECT current_setting('request.jwt.claims', true)::json->>'email';

重要

  • 请求头名称全部小写。例如,如果请求发送了 User-Agent: x,则可以通过 current_setting('request.headers', true)::json->>'user-agent' 获取。

  • request.jwt.claims 中的 role 默认值为 db-anon-role 的值。

  • 设置在事务提交后不会变为 NULL,而是被设置为一个空字符串 ''

    • 这被认为是 PostgreSQL 的预期行为。有关更多详细信息,请参阅 此讨论

    • 为了避免这种不一致,您可以创建一个包装函数,例如

    CREATE FUNCTION my_current_setting(text) RETURNS text
    LANGUAGE SQL AS $$
      SELECT nullif(current_setting($1, true), '');
    $$;
    

请求路径和方法

路径和方法存储为 text

SELECT current_setting('request.path', true);

SELECT current_setting('request.method', true);

请求角色和搜索路径

由于 用户模拟,PostgREST 设置了标准的 role。您可以通过不同的方式获取它

SELECT current_role;

SELECT current_user;

SELECT current_setting('role', true);

此外,它还根据 db-schemasdb-extra-search-path 设置 search_path

响应头

您可以设置 response.headers 以将头添加到 HTTP 响应中。例如,此语句将向响应添加缓存头

-- tell client to cache response for two days

SELECT set_config('response.headers',
  '[{"Cache-Control": "public"}, {"Cache-Control": "max-age=259200"}]', true);
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Cache-Control: no-cache, no-store, must-revalidate

请注意,response.headers 应该设置为一个包含单个键对象的数组,而不是单个包含多个键的对象。这是因为诸如 Cache-ControlSet-Cookie 之类的头在设置多个值时需要重复。对象不允许重复键。

注意

PostgREST 提供的头,例如 Content-TypeLocation 等,可以通过这种方式覆盖。请注意,无论覆盖的 Content-Type 响应头如何,内容仍将转换为 JSON,除非您使用 媒体类型处理程序

响应状态码

您可以设置 response.status 来覆盖 PostgREST 提供的默认状态码。例如,以下函数将替换默认的 200 状态码。

create or replace function teapot() returns json as $$
begin
  perform set_config('response.status', '418', true);
  return json_build_object('message', 'The requested entity body is short and stout.',
                           'hint', 'Tip it over and pour it out.');
end;
$$ language plpgsql;
curl "http://localhost:3000/rpc/teapot" -i
HTTP/1.1 418 I'm a teapot

{
  "message" : "The requested entity body is short and stout.",
  "hint" : "Tip it over and pour it out."
}

如果状态码是标准的,PostgREST 将完成状态消息(本例中为 **I’m a teapot**)。

模拟角色设置

PostgreSQL 应用连接角色(身份验证器)设置。此外,PostgREST 将应用 模拟角色 设置作为事务范围的设置。这允许更细粒度的控制角色执行的操作。

例如,考虑 statement_timeout。它允许您中止任何超过指定时间执行的语句。默认情况下它被禁用。

ALTER ROLE authenticator SET statement_timeout TO '10s';
ALTER ROLE anonymous SET statement_timeout TO '1s';

使用上述设置,所有用户都将获得 10 秒的全局语句超时,而 匿名 用户将获得 1 秒的超时。

具有特权上下文的设置

默认情况下不会应用具有需要特权的上下文的设置。这是为了避免出现权限错误。有关更多详细信息,请参阅 理解 Postgres 参数上下文

但是,从 PostgreSQL 15 开始,您可以使用以下方法授予这些设置的特权:

GRANT SET ON PARAMETER <setting> TO <authenticator>;

函数设置

除了 模拟角色设置 之外,PostgREST 还将应用函数设置作为事务范围的设置。这允许函数设置覆盖模拟和连接角色设置。

CREATE OR REPLACE FUNCTION myfunc()
RETURNS void as $$
  SELECT pg_sleep(3); -- simulating some long-running process
$$
LANGUAGE SQL
SET statement_timeout TO '4s';

调用上述函数时(请参阅 函数作为 RPC),语句超时将为 4 秒。

注意

只有由配置 db-hoisted-tx-settings 提升的事务才会应用。

主查询

主查询是通过请求 表和视图函数作为 RPC 生成的。所有生成的查询都使用预备语句(db-prepared-statements)。

事务结束

如果事务没有失败,它将始终以 COMMIT 结束。除非 db-tx-end 配置为在任何情况下都回滚,或者使用 事务结束偏好 有条件地回滚。这对于测试目的很有用。

中止事务

任何数据库故障(例如约束失败)都会导致事务回滚。您也可以在函数内部引发错误以导致回滚。

预请求

预请求是一个函数,它可以在设置事务范围设置后,并在主查询之前运行。它通过db-pre-request启用。

这提供了一个修改设置或引发异常以阻止请求完成的机会。

通过预请求设置标头

例如,让我们为来自 Internet Explorer(6 或 7)浏览器的所有请求添加一些缓存标头。

create or replace function custom_headers()
returns void as $$
declare
  user_agent text := current_setting('request.headers', true)::json->>'user-agent';
begin
  if user_agent similar to '%MSIE (6.0|7.0)%' then
    perform set_config('response.headers',
      '[{"Cache-Control": "no-cache, no-store, must-revalidate"}]', false);
  end if;
end; $$ language plpgsql;

-- set this function on postgrest.conf
-- db-pre-request = custom_headers

现在,当您对表或视图发出 GET 请求时,您将获得缓存标头。

curl "http://localhost:3000/people" -i \
 -H "User-Agent: Mozilla/4.01 (compatible; MSIE 6.0; Windows NT 5.1)"