Given this schema:
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
));
I'm trying to insert a post using:
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;
$$;
However, I get:
'new row violates row-level security policy for table "tags"'
If I get rid of the RLS policy, it seems to work.
I also am able to change the langauge to plpgsql without CTE, and it seems to work:
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;
$$;
I want to write up some more complicated transactions, but I need to use sql and CTE for other purposes.
Does RLS not work with CTE transactions?
That's what composite secondary-keys (aka
UNIQUE CONSTRAINT) indexes are for.Like so:
pid, or ambiguous names likeid)DELETE CASCADEis fine, butON UPDATE CASCADEis not.auth.uid()extension - I don't recommend this because it means introducing a hard dependency between your storage-layer and your application-code layer - this means that you won't be able to easily manually edit data in the database using other tooling unless you're careful to disable allDEFAULTconstraints referencing Supabase extensions.tags.nameandtags.post_idcolumns are missing explicitNOT NULLconstraints. While the columns are implicitlyNOT NULLbecause they're part of thePRIMARY KEYif you were to ever change the PK definition you'll need to add theNOT NULLto your db-schema-in-source-control otherwise a redeployment will see them havingNULLable columns (and you are keeping your db-schema in source-control, right?)tags.post_idis going to be more selective thantags.name, sopost_idshould go first.CREATE TABLEstatements's parts into columns, it makes them significantly more readable: