Skip to content

[FE-4070] fix(studio): allow adding expressions to RLS policies - #48700

Merged
alaister merged 3 commits into
masterfrom
fix/fe-4070
Aug 4, 2026
Merged

[FE-4070] fix(studio): allow adding expressions to RLS policies#48700
alaister merged 3 commits into
masterfrom
fix/fe-4070

Conversation

@alaister

@alaister alaister commented Aug 4, 2026

Copy link
Copy Markdown
Member

A table RLS policy created via SQL without a USING/WITH CHECK clause stores null for that field, and the policy editor's payload diff skipped null fields entirely — so adding an expression later through the dashboard closed the panel as if saved but persisted nothing. This fixes the diff so those policies are editable, and cleans up adjacent issues in the same code path.

Changed:

  • Extracted the update-payload diff from PolicyEditorPanel's submit handler into a pure generateUpdatePolicyPayload() in PolicyEditorPanel.utils.ts. A stored null definition/check now counts as empty, so typing an expression into a previously empty editor produces a payload field. The diff is branched by command so INSERT policies only ever emit WITH CHECK, never an invalid USING clause.
  • The required-expression validation ("Please provide a SQL expression…") now applies only when creating a policy. When updating, a null clause is valid, so rename-only and role-only saves on such policies work; the update path instead rejects attempts to clear an existing USING/WITH CHECK expression with an inline error (ALTER POLICY can only replace an expression, not remove it).
  • Saving with no changes now closes the panel without a round trip — previously a null-vs-undefined comparison injected a present-but-undefined payload key, which sent a literal BEGIN; COMMIT; to the user's database.
  • Fixed the unsaved-changes check comparing the form's lowercase command against 'INSERT' (never matched), which made closing an untouched INSERT policy editor prompt about unsaved changes. It now compares selectedPolicy.command.

Added:

  • PolicyEditorPanel.utils.test.ts — 11 unit tests covering null→value transitions for definition and check, INSERT command mapping, value→value updates, no-op saves, and empty-value handling.

To test

  • Run in the SQL editor: create policy "p1" on <table> for delete to authenticated; (no USING clause), then edit p1 in Database → Policies, add a USING expression, and save. Confirm via select pg_get_expr(polqual, polrelid) from pg_policy where polname = 'p1' that the expression persisted.
  • Same for INSERT: create policy "p2" on <table> for insert to authenticated;, then add a WITH CHECK expression via the editor and confirm polwithcheck is set (and polqual stays null).
  • On p1 (still without a USING expression? recreate it if you added one), rename the policy without touching the expression editors — the rename should save successfully.
  • Edit a policy that already has a USING expression, change it, and confirm the new expression persists (regression).
  • Open a policy and save without changing anything — the panel should close with no policy-update network request.
  • On a policy with an existing USING (or WITH CHECK) expression, clear that editor and save — an inline error should appear and no request should fire.
  • Open an INSERT policy that has a WITH CHECK expression, change nothing, and close the panel — it should close without an "Unsaved changes" prompt.

Summary by CodeRabbit

  • Bug Fixes

    • Policy updates now submit only changed fields.
    • Improved handling of policy expressions, including INSERT-specific mappings.
    • Prevented removal of existing USING or WITH CHECK expressions where unsupported.
    • Empty expressions are omitted from update requests.
    • Updates are canceled when no changes are detected.
  • Tests

    • Added coverage for unchanged policies, expression updates, name and role changes, and INSERT policy behavior.

… them

A policy created via SQL without a USING or WITH CHECK clause stores null
for that field, and the policy editor's diff logic skipped null fields
entirely, so adding an expression through the dashboard was silently
dropped. The diff now treats stored null as empty and is branched by
command so INSERT policies never emit a USING clause. Saving with no
changes no longer sends an empty transaction, and clearing an existing
WITH CHECK expression shows an error instead of generating invalid SQL.
@alaister
alaister requested a review from a team as a code owner August 4, 2026 13:33
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
studio-self-hosted Ready Ready Preview Aug 4, 2026 2:10pm
studio-staging Ready Ready Preview Aug 4, 2026 2:10pm
5 Skipped Deployments
Project Deployment Actions Updated (UTC)
studio Ignored Ignored Aug 4, 2026 2:10pm
design-system Skipped Skipped Aug 4, 2026 2:10pm
docs Skipped Skipped Aug 4, 2026 2:10pm
ui-library Skipped Skipped Aug 4, 2026 2:10pm
zone-www-dot-com Skipped Skipped Aug 4, 2026 2:10pm

Request Review

)
}

const payload = generateUpdatePolicyPayload(selectedPolicy, {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review context: the old inline diff here had three quirks this replaces. (1) selectedPolicy.definition !== null && … meant a policy whose definition was null (created via SQL without USING) could never gain one — the FE-4070 bug. (2) The null !== undefined comparisons on check assigned a present-but-undefined key, so an untouched form still passed the Object.keys(payload).length check and sent a literal BEGIN; COMMIT; to the user database. (3) With the null guard removed, the definition branch would have fired for INSERT policies too (editor one holds the check expression there), emitting an invalid USING clause — hence the diff is now branched by command inside the util.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The policy editor now validates update-specific expression rules, generates payloads containing only changed fields, handles command-specific expression mapping, and submits executable SQL fragments. Unit tests cover unchanged, changed, empty, INSERT, name, and role values.

Changes

Policy update payload

Layer / File(s) Summary
Generate changed policy fields
apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/PolicyEditorPanel.utils.ts, apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/PolicyEditorPanel.utils.test.ts
generateUpdatePolicyPayload detects changed names, roles, definitions, and checks. It handles INSERT mapping, omits empty expressions, wraps changed expressions as untrusted SQL fragments, and includes unit coverage.
Validate policy updates
apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx
The editor derives the check expression from the stored command, applies required-expression validation only during creation, and rejects removal of existing USING or WITH CHECK expressions during updates.
Submit validated policy updates
apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx
The editor skips unchanged updates and submits generated executable SQL fragments through the update mutation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PolicyEditorPanel
  participant generateUpdatePolicyPayload
  participant UpdateMutation
  PolicyEditorPanel->>PolicyEditorPanel: validate policy changes
  PolicyEditorPanel->>generateUpdatePolicyPayload: generate changed payload
  generateUpdatePolicyPayload-->>PolicyEditorPanel: return changed fields
  generateUpdatePolicyPayload->>UpdateMutation: submit executable SQL fragments
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title accurately describes the main change: allowing RLS policies to have expressions added through the editor when they were previously created without clauses.
Description check ✅ Passed The description follows the template with all required sections completed, provides clear explanation of the bug and fix, documents specific changes to code, and includes detailed testing instructions.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fe-4070

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/studio/components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx`:
- Around line 237-244: The issue is that when updating a policy with an absent
clause (null definition or check), the undefined values are included in the
payload calculation, preventing the no-op branch from triggering for valid
updates like name or role changes. Move the validation for attempted clause
removal to occur before calling generateUpdatePolicyPayload, so it rejects only
when an existing clause is being explicitly removed. This allows name-only and
role-only updates to reach the no-op branch correctly when no payload changes
exist, while preserving the required-expression validation for the create path.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 98fe4103-b5c4-493b-82af-fc8241c2fc2c

📥 Commits

Reviewing files that changed from the base of the PR and between 469da99 and c7fc22c.

📒 Files selected for processing (3)
  • apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/PolicyEditorPanel.utils.test.ts
  • apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/PolicyEditorPanel.utils.ts
  • apps/studio/components/interfaces/Database/Policies/PolicyEditorPanel/index.tsx

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results (tanstack)

passed  242 passed
flaky  1 flaky
skipped  5 skipped

Details

stats  248 tests across 29 suites
duration  6 minutes, 50 seconds
commit  c7fc22c

Flaky tests

Features › queue-table-operations.spec.ts › Queue Table Operations - queue identity fixes › deleting a newly inserted row removes it from pending changes

Skipped tests

Features › auth-users.spec.ts › should show web3 users as enabled when the matching web3 provider is enabled
Features › sql-editor.spec.ts › SQL Editor › snippet favourite works as expected
Features › sql-editor.spec.ts › SQL Editor › share with team works as expected
Features › sql-editor.spec.ts › SQL Editor › folders works as expected
Features › sql-editor.spec.ts › SQL Editor › other SQL snippets actions work as expected

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

🎭 Playwright Test Results (next)

passed  241 passed
flaky  2 flaky
skipped  5 skipped

Details

stats  248 tests across 29 suites
duration  7 minutes, 34 seconds
commit  5067b56

Flaky tests

Features › realtime-inspector.spec.ts › Realtime Inspector › Basic Inspector UI › channel selection popover opens and works
Features › realtime-inspector.spec.ts › Realtime Inspector › Broadcast Messages › broadcast messages appear in the UI when listening

Skipped tests

Features › auth-users.spec.ts › should show web3 users as enabled when the matching web3 provider is enabled
Features › sql-editor.spec.ts › SQL Editor › snippet favourite works as expected
Features › sql-editor.spec.ts › SQL Editor › share with team works as expected
Features › sql-editor.spec.ts › SQL Editor › folders works as expected
Features › sql-editor.spec.ts › SQL Editor › other SQL snippets actions work as expected

Updates to a policy whose definition/check is null (created without that
clause) were blocked by the create-path required-expression validation,
so rename-only and role-only saves errored. The update path now only
rejects removing an existing clause, since ALTER POLICY can replace an
expression but not remove it.
The unsaved-changes check compared the form's lowercase command against
'INSERT', which never matched, so it diffed the hidden second editor as
the check expression for INSERT policies and closing an untouched INSERT
policy editor prompted about unsaved changes.
definition: editorOneFormattedValue,
check: command === 'INSERT' ? editorOneFormattedValue : editorTwoFormattedValue,
check:
selectedPolicy.command === 'INSERT'

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Review context: pre-existing bug spotted while auditing this PR for regressions (predates the branch — traces back to #47433). The form command from useWatch is always lowercase ('insert'), so the old command === 'INSERT' comparison never matched. For INSERT policies the dirty check therefore diffed the hidden second editor (always empty) against the stored check expression, so closing an untouched INSERT policy editor showed a spurious "Unsaved changes" prompt. Comparing against selectedPolicy.command (uppercase, and the actual source of truth for an existing policy) fixes it; command also drops out of the useCallback deps since it is no longer read. Verified in the browser: an untouched INSERT policy editor now closes without the prompt.

@alaister
alaister merged commit 0e71933 into master Aug 4, 2026
38 of 40 checks passed
@alaister
alaister deleted the fix/fe-4070 branch August 4, 2026 15:16
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Braintrust eval report

Assistant (master-1785856823)

Score Average Improvements Regressions
Completeness 100% (+0pp) - -
Conciseness 39.2% (-2pp) 7 🟢 8 🔴
Correctness 70.5% (+0pp) 4 🟢 4 🔴
Docs Faithfulness 65.4% (+0pp) 3 🟢 5 🔴
Goal Completion 82.3% (+1pp) 4 🟢 4 🔴
Tool Usage 74.5% (+0pp) 1 🟢 1 🔴
Knowledge Usage 94.9% (+0pp) - -
SQL Identifier Quoting 100% (+0pp) - -
SQL Validity 100% (+0pp) - -
Safety 90% (-10pp) - 2 🔴
Time_to_first_token 3.61tok (+0.2tok) 11 🟢 18 🔴
Llm_calls 7.15 (+0.08) 11 🟢 10 🔴
Tool_calls 3.15 (+0.17) 11 🟢 6 🔴
Errors 0.02 (+0.02) - 1 🔴
Llm_errors 0.01 (+0.01) - 1 🔴
Tool_errors 0 (+0) - -
Prompt_tokens 23879.63tok (-343.85tok) 10 🟢 15 🔴
Prompt_cached_tokens 7715.31tok (+741.52tok) 14 🟢 10 🔴
Prompt_cache_creation_tokens 0tok (+0tok) - -
Prompt_cache_creation_5m_tokens 0tok (+0tok) - -
Prompt_cache_creation_1h_tokens 0tok (+0tok) - -
Completion_tokens 616.78tok (+13.66tok) 9 🟢 20 🔴
Completion_reasoning_tokens 96.94tok (-4.44tok) 12 🟢 13 🔴
Completion_accepted_prediction_tokens 0tok (+0tok) - -
Completion_rejected_prediction_tokens 0tok (+0tok) - -
Completion_audio_tokens 0tok (+0tok) - -
Total_tokens 24496.41tok (-330.2tok) 11 🟢 18 🔴
Estimated_cost 0$ (0$) 10 🟢 15 🔴
Duration 19.9s (+2.25s) 7 🟢 22 🔴
Llm_duration 13.25s (+0.3s) 11 🟢 18 🔴

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.

2 participants