Thanks to visit codestin.com
Credit goes to github.com

Skip to content

perf(rls): wrap auth.uid()/current_setting() in (select …) for per-statement evaluation - #3121

Open
dmitrymaranik wants to merge 2 commits into
onlook-dev:mainfrom
dmitrymaranik:perf/wrap-rls-initplan
Open

perf(rls): wrap auth.uid()/current_setting() in (select …) for per-statement evaluation#3121
dmitrymaranik wants to merge 2 commits into
onlook-dev:mainfrom
dmitrymaranik:perf/wrap-rls-initplan

Conversation

@dmitrymaranik

@dmitrymaranik dmitrymaranik commented Jun 29, 2026

Copy link
Copy Markdown

Postgres re-evaluates a bare auth.uid() / current_setting(…) in an RLS policy once per row. Wrapping it in a scalar subquery — (select auth.uid()) — makes it an InitPlan the planner evaluates once per statement and caches. It's predicate-equivalent (row visibility unchanged) and the optimization Supabase documents for RLS.

This adds a migration that wraps the 16 affected per-user policies:

  • Predicate-equivalent — row visibility is unchanged; only when the auth call is evaluated changes (once per statement vs once per row).
  • Covers both USING and WITH CHECK (the write path runs per inserted/updated row too).
  • Verified with pgrls (an open-source RLS linter) on Postgres 16: the PERF001 findings clear to 0 after the change, nothing else altered.
  • The SQL is pgrls fix --rule PERF001 output, added as a new migration (0020_wrap_rls_perf_initplan.sql).

Happy to adjust the migration filename/placement to your conventions.

Summary by CodeRabbit

  • Bug Fixes
    • Improved permission-check performance across key areas of the app, reducing repeated authentication lookups so actions feel faster and more responsive.
    • Optimized access control evaluation for file transfer and project-related workflows, including invitations, canvases, user settings, and user profile access.

@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

@dmitrymaranik is attempting to deploy a commit to the Onlook Team on Vercel.

A member of the Team first needs to authorize it.

@vercel
vercel Bot temporarily deployed to Preview – docs-onlook June 29, 2026 22:01 Inactive
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs-onlook Skipped Skipped Jun 30, 2026 6:47am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

A new Supabase migration rewrites RLS policies on storage and public tables by replacing direct auth.uid() calls with (select auth.uid()) subqueries in USING and WITH CHECK clauses.

Changes

RLS InitPlan Performance Migration

Layer / File(s) Summary
Storage object policy rewrites
apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql
The migration header and storage.objects policies update delete, insert, and select predicates to use (select auth.uid()) instead of direct auth.uid() calls.
Project invitation policy rewrite
apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql
public.project_invitations delete, insert, select, and update policies wrap auth.uid() in (select auth.uid()), including the correlated auth.users lookup in the delete predicate.
Remaining public table policy rewrites
apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql
public.user_canvases, public.user_projects, public.user_settings, and public.users policies apply the same (select auth.uid()) wrapping across their relevant predicates.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops through policy rows,
Wrapping uid() calls with SELECT bows,
InitPlan caches the auth away,
No per-row re-eval today!
🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: wrapping RLS auth calls in scalar subqueries for per-statement evaluation.
Description check ✅ Passed The description covers the change, rationale, testing, and notes; only related issues and type-of-change sections are missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql (1)

8-9: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Wrap the auth.uid() inside the EXISTS too. Line 9 still calls auth.uid() directly in the correlated subquery (WHERE users.id = auth.uid()), so that part can still be evaluated per row. Change it to (SELECT auth.uid()) as well for the same InitPlan caching used elsewhere here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql` around
lines 8 - 9, The project_invitations_delete_policy still calls auth.uid()
directly inside the correlated EXISTS subquery, so update that predicate to use
the same InitPlan-wrapped form as the rest of the policy. In the ALTER POLICY
statement for project_invitations_delete_policy, change the users.id comparison
inside the EXISTS clause to compare against (SELECT auth.uid()) instead of
auth.uid(), keeping the rest of the USING expression unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql`:
- Around line 1-72: The migration is missing the same InitPlan optimization for
the project_invitations read/update policies. Update the ALTER POLICY statements
for project_invitations_select_policy and project_invitations_update_policy to
wrap auth.uid() in a (select ...) just like the other PERF001 fixes, including
inside the auth.users subquery, so the predicates stay equivalent while enabling
statement-level caching.

---

Nitpick comments:
In `@apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql`:
- Around line 8-9: The project_invitations_delete_policy still calls auth.uid()
directly inside the correlated EXISTS subquery, so update that predicate to use
the same InitPlan-wrapped form as the rest of the policy. In the ALTER POLICY
statement for project_invitations_delete_policy, change the users.id comparison
inside the EXISTS clause to compare against (SELECT auth.uid()) instead of
auth.uid(), keeping the rest of the USING expression unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b2d84b8c-3a35-444f-aca4-5de6a54af4cc

📥 Commits

Reviewing files that changed from the base of the PR and between 936b015 and 0ed889e.

📒 Files selected for processing (1)
  • apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql

Comment thread apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql Outdated
@dmitrymaranik

Copy link
Copy Markdown
Author

Good catch — you're right that the EXISTS-nested auth.uid() calls were still bare (the tool I used, pgrls, wraps top-level policy calls only). I've updated the migration to wrap every auth.uid() call, including the ones nested inside correlated EXISTS subqueries (and the policies that had only nested calls). It's now 21 ALTER POLICY rewrites; verified on Postgres 16 that 0 bare auth.*() calls remain in any policy predicate, with no double-wrapping. Still predicate-equivalent — row visibility is unchanged.

@vercel
vercel Bot temporarily deployed to Preview – docs-onlook June 30, 2026 06:47 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql (1)

41-45: 🚀 Performance & Scalability | 🔵 Trivial

InitPlan benefit is partially negated by user_has_project_access, which still calls bare auth.uid() per row.

The direct (select auth.uid()) wrapping here is correct, but these OR branches also call user_has_project_access(...), a SECURITY DEFINER plpgsql function defined in 0006_rls.sql that invokes auth.uid() once per call — and the function is evaluated per row. So PERF001 on the policy predicate is resolved, but the per-row auth.uid() cost remains inside the helper. If the perf goal is end-to-end, consider stabilizing the function (e.g. passing the uid in, or wrapping its internal lookup) in a follow-up. Out of scope for this migration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql` around
lines 41 - 45, The policy changes correctly wrap auth.uid() in an InitPlan, but
user_has_project_access still evaluates a bare auth.uid() per row inside the
SECURITY DEFINER helper defined in 0006_rls.sql. To complete the perf
improvement, update user_has_project_access to avoid calling auth.uid()
internally on every invocation, for example by passing the caller uid into the
function or by caching the lookup once inside the function, and then keep the
user_projects_delete_policy and user_projects_select_policy predicates using
that stabilized helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql`:
- Around line 41-45: The policy changes correctly wrap auth.uid() in an
InitPlan, but user_has_project_access still evaluates a bare auth.uid() per row
inside the SECURITY DEFINER helper defined in 0006_rls.sql. To complete the perf
improvement, update user_has_project_access to avoid calling auth.uid()
internally on every invocation, for example by passing the caller uid into the
function or by caching the lookup once inside the function, and then keep the
user_projects_delete_policy and user_projects_select_policy predicates using
that stabilized helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 23b9a0d8-2c99-4394-9a75-e617e6035349

📥 Commits

Reviewing files that changed from the base of the PR and between 0ed889e and a8424e1.

📒 Files selected for processing (1)
  • apps/backend/supabase/migrations/0020_wrap_rls_perf_initplan.sql

@dmitrymaranik

Copy link
Copy Markdown
Author

Good catch, and agreed it's out of scope for this migration (as the bot notes). This change wraps the policy predicates' direct auth.uid() calls — which is exactly what the InitPlan optimization targets and what the tool I used rewrites.

The user_has_project_access(...) helper calling auth.uid() internally is a separate surface — a SECURITY DEFINER function body, not a policy predicate — so a mechanical predicate-wrap doesn't (and shouldn't) touch it. Stabilizing that helper (passing the uid in, or caching it once inside the function) is a worthwhile follow-up for the maintainers, but independent of this migration. The InitPlan win on the direct calls stands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant