| From: | Joao Rito <joaovictor(dot)rito(at)gmail(dot)com> |
|---|---|
| To: | jian he <jian(dot)universality(at)gmail(dot)com> |
| Cc: | Kirill Reshke <reshkekirill(at)gmail(dot)com>, Bryan Green <dbryan(dot)green(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org |
| Subject: | Re: COPY FROM with RLS |
| Date: | 2026-11-10 13:30:16 |
| Message-ID: | 20261110133016.61814-1-joaovictor.rito@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi Jian,
I reviewed v16 and ran some tests. I tried to look at areas that were not
covered in the thread yet.
Setup: master at 952bab8b74, Debian bookworm container, built with
--enable-cassert --enable-debug. v16 applies with small offsets, the build
has no warnings, and make check passes (243 tests). I ran the same probe
script on master and on the patched build. As expected, on master every
non-owner COPY FROM below fails with "COPY FROM not supported with row-level
security".
Things that work the same as INSERT:
- Triggers: with BEFORE/AFTER ROW, BEFORE STATEMENT and an AFTER STATEMENT
trigger with REFERENCING NEW TABLE, a 3-row COPY fires each row trigger 3
times, each statement trigger once, and the transition table has 3 rows.
INSERT gives the same counts.
- A BEFORE ROW trigger that changes the owner column so the row fails the
policy: COPY fails with the RLS error, same as INSERT. So the check runs
on the row after the triggers.
- Partitioned table with a permissive policy on the parent and a stricter
INSERT policy on the leaf: COPY into the parent succeeds and COPY into the
leaf fails, the same as INSERT through the parent and the leaf.
- FORCE ROW LEVEL SECURITY: the owner's COPY is checked. A BYPASSRLS role is
not checked.
- Column privileges: with only INSERT (a), COPY t (a) works and COPY t (a,
secret) fails with "permission denied", same as master. So the DEFAULT
VALUES statement does not need extra privileges.
- COPY ... WHERE: a row removed by WHERE that would fail the policy does not
raise an error.
- A deferred constraint trigger fires at commit.
1. The internal INSERT is visible to monitoring extensions
With pg_stat_statements.track = 'all', one COPY FROM on an RLS table leaves
two entries:
toplevel | calls | rows | query
----------+-------+------+-------------------------------------
t | 1 | 3 | COPY t (a) FROM STDIN
f | 1 | 0 | INSERT INTO public.t DEFAULT VALUES
With auto_explain.log_nested_statements = on, the server log also gets a
plan for it:
LOG: duration: 0.000 ms plan:
Query Text: INSERT INTO public.t DEFAULT VALUES
Insert on t (cost=0.00..0.01 rows=0 width=0)
-> Result (cost=0.00..0.01 rows=1 width=36)
This comes from running the query through
ExecutorStart/ExecutorFinish/ExecutorEnd, which call the executor hooks.
With track = 'top' only the COPY entry is there. It is not wrong, but a DBA
who sees "INSERT INTO public.t DEFAULT VALUES" with rows = 0 may think some
application ran it. Maybe this is fine and only needs a comment in the code.
Or maybe the query should not get a queryId, so pg_stat_statements skips it.
I am not sure which is better, so I wanted to mention it.
2. ON_ERROR ignore and the missing line number
COPY ... WITH (ON_ERROR ignore) skips a bad input value, but an RLS
violation still aborts the whole COPY. I think that is right, because
ON_ERROR only covers soft input errors. But the error only shows "CONTEXT:
COPY t", without the line number, so with a large file there is no easy way
to find the row that failed. This is the same point Kirill raised for errors
inside a policy function. I think it applies to the violation error too, and
it is probably the most common error users will see from this feature.
3. Performance
1M rows from a file, fsync = off, a simple stable policy (owner =
current_user), times for one COPY:
- table owner, RLS not applied: 329 ms
- non-owner, RLS applied: 440 ms
- non-owner, INSERT ... SELECT of the same rows: 1170 ms
So multi-insert batching still works with a non-volatile policy, and COPY
stays much faster than the INSERT workaround.
Regards,
Joao Rito
| From | Date | Subject | |
|---|---|---|---|
| Next Message | David Geier | 2026-11-10 13:58:24 | Re: Hash a ScalarArrayOpExpr whose array is fixed for one execution |
| Previous Message | Tatsuya Kawata | 2026-11-10 13:28:06 | Re: [PATCH] Add memory/disk usage for Function Scan nodes in EXPLAIN |