使用公用表表达式实现行级安全性

问题描述 投票:0回答:1

鉴于此模式:

CREATE TABLE posts (
  id uuid NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
  title text NOT NULL CHECK (char_length(title) > 2),
  author uuid NOT NULL DEFAULT auth.uid() REFERENCES profiles(id) 
    ON DELETE CASCADE ON UPDATE CASCADE,
  content text NOT NULL CHECK (char_length(content) > 3),
  created_at timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE tags (
  name text,
  pid uuid REFERENCES posts(id) ON DELETE CASCADE ON UPDATE CASCADE,
  created_at timestamptz NOT NULL DEFAULT now(),
  PRIMARY KEY (name, pid)
);

CREATE POLICY "rls_tags_read_public"
  ON tags FOR SELECT
  USING (true);

CREATE POLICY "rls_tags_create_authenticated_own_posts"
  ON tags FOR INSERT TO "authenticated"
  WITH CHECK (EXISTS (
    SELECT 1 FROM posts p WHERE p.author = auth.uid() 
    AND p.id = pid
  ));

我正在尝试使用以下方式插入帖子:

CREATE OR REPLACE FUNCTION insert_post(
  title text,
  content text,
  tags text[]
)
RETURNS SETOF posts
LANGUAGE sql
AS $$
  WITH new_post AS (
    INSERT INTO posts (title, content)
    VALUES (title, content)
    RETURNING *
  ),
  insert_tags AS (
    INSERT INTO tags (name, pid)
    SELECT unnest(insert_post.tags), id FROM new_post
  )
  SELECT * FROM new_post;
$$;

但是,我得到:

'new row violates row-level security policy for table "tags"'

如果我取消 RLS 政策,它似乎会起作用。

我还可以在没有 CTE 的情况下将语言更改为

plpgsql
,而且似乎有效:

CREATE OR REPLACE FUNCTION insert_post(
  title text,
  content text,
  tags text[]
)
RETURNS SETOF posts
LANGUAGE plpgsql
AS $$
DECLARE
  new_post posts%ROWTYPE;
BEGIN
  INSERT INTO posts (title, content)
  VALUES (title, content)
  RETURNING * INTO new_post;

  INSERT INTO tags (name, pid)
  SELECT unnest(tags), new_post.id;

  RETURN QUERY SELECT * FROM posts WHERE id = new_post.id;
END;
$$;

我想写一些更复杂的交易,但我需要将

sql
CTE
用于其他目的。

RLS 不适用于 CTE 交易吗?

J

postgresql common-table-expression supabase row-level-security
1个回答
0
投票

我实际上重新看了看我的桌子。我检查外键的原因是为了确认它是包含帖子作者 ID 的同一个表。因此,除非您将标签添加到帖子中,并且您是该帖子的作者,否则您无法添加标签。我的问题仍然存在,我无法使用语言 sql 来实现此功能。

这就是复合 辅助键(又名

UNIQUE CONSTRAINT
)索引的用途。

像这样:

  • 还有...
    • 任何人都不应使用神秘的列名称,如
      pid
      或不明确的名称,如
      id
      )
    • 你的 PK 应该是不可变的。拥有
      DELETE CASCADE
      很好,但是
      ON UPDATE CASCADE
      则不然。
    • 您似乎正在使用 Supabase 的
      auth.uid()
      扩展 - 我不推荐这样做,因为这意味着在存储层和应用程序代码层之间引入硬依赖关系 - 这意味着您将无法轻松手动编辑使用其他工具处理数据库中的数据,除非您小心地禁用所有引用 Supabase 扩展的
      DEFAULT
      约束。
    • 您的
      tags.name
      tags.post_id
      列缺少显式
      NOT NULL
      约束。虽然这些列是隐式的
      NOT NULL
      因为它们是
      PRIMARY KEY
      的一部分,如果您要更改 PK 定义,则需要将
      NOT NULL
      添加到您的 db-schema-in-source-control 否则重新部署将看到它们具有
      NULL
      可用的列(并且您将数据库模式保留在源代码控制中,对吗?)
    • 复合键的第一列应该始终是最具选择性的:人们会认为
      tags.post_id
      tags.name
      更具选择性,因此
      post_id
      应该排在第一位。
    • 考虑养成将
      CREATE TABLE
      语句的部分对齐到列中的习惯,这使它们的可读性显着提高:
CREATE TABLE posts (
  post_id        uuid        NOT NULL DEFAULT uuid_generate_v4(),
  title          text        NOT NULL CHECK ( char_length( title ) > 2 ),
  author_user_id uuid        NOT NULL DEFAULT auth.uid() REFERENCES profiles( user_id ) ON DELETE CASCADE,
  content        text        NOT NULL CHECK ( char_length( content ) > 3 ),
  created_at     timestamptz NOT NULL DEFAULT now(),

  CONSTRAINT PK_posts PRIMARY KEY ( post_id ),
  
  CONSTRAINT UK_posts_author UNIQUE ( post_id, author_user_id ) /* Referenced by tags.FK_tags_posts */
);

CREATE TABLE tags (
  name           text        NOT NULL,
  post_id        uuid        NOT NULL,
  created_at     timestamptz NOT NULL DEFAULT now(),
  author_user_id uuid        NOT NULL DEFAULT auth.uid() REFERENCES profiles( user_id ) ON DELETE CASCADE,

  CONSTRAINT PK_tags PRIMARY KEY ( post_id, name ),
  
  CONSTRAINT FK_tags_posts FOREIGN KEY ( post_id, author_user_id ) REFERENCES posts ( post_id, author_user_id ) /* This will use `UK_posts_author` */
);
© www.soinside.com 2019 - 2024. All rights reserved.