Skip to content

refactor(access): simplify the roles and allow-all code from #435 - #436

Merged
huyplb merged 1 commit into
mainfrom
refactor/access-simplify
Sep 27, 2026
Merged

huyplb merged 1 commit into
mainfrom
refactor/access-simplify

Conversation

@huyplb

@huyplb huyplb commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

This is a readability and maintainability pass over #435. It came out of a /simplify review from four angles: reuse, simplification, efficiency and altitude. Behaviour is unchanged. The same unit, component and live e2e tests pass.

What changed

Efficiency, the one that matters at size

  • findAllowAllByName indexes privileges by grantee once, and answers each role once, however many accounts share it.
  • Before this, both lists called findAllowAll per account, and each call re-read the whole privilege list. That's about 70 million comparisons for 1,000 MySQL accounts with *.* grants (70 rows each), run on the UI thread on every catalog load.
  • A new test checks the bulk answer equals the per-account answer for every principal.

One way to do each thing

  • Name matching: there is now one key, principalKey (quotes stripped, case folded). privilegesForPrincipal, reconcileDbAccess and the allow-all index all use it. Before, there were three spellings of it.
  • MySQL quoting: formatDbGrantee and the new SET DEFAULT ROLE statements use mysqlQuote / mysqlAccount / mysqlRoleRef from user-sql-helpers, the same helpers CREATE USER uses. The MariaDB "role has no host" rule had already been copied into a second place.
  • GRANT target rule: the MySQL and ClickHouse branches of buildGrantRevokeSql carried the same copied *.* / db.* rule. They are now one branch.

Right place

  • SQL Server's implied fixed-role rows (db_owner → CONTROL) are now their own function, impliedFixedRolePrivileges. The server calls it beside reconcileDbAccess, instead of the rows being hidden inside a function documented as "make the two catalogs agree".
  • reconcileDbAccess uses Sets rather than list scans, because a widely held role has thousands of members.

Easier to read

  • DatabaseAccessModal:
    • The 140-line privilege-table block is now a file-local PrivilegeGroupRows component.
    • One confirmRevoke replaces three copies of the build-REVOKE, then error or confirm, flow.
    • The membership role is derived rather than kept in sync by an effect with five deps.
    • The typed-confirmation field resets where the confirmation opens.
    • granteeKindOf, which returned its input unchanged, is gone.
  • allPrivilegeTargets: returns its lists directly, and drops an objectSchema field that was always null.
  • roleMembershipStatements: a plain if/else instead of a nested ternary of object literals.
  • UserManagement: any MySQL-family name parses as user@host. A MariaDB role has no @ and parses to itself, so the role special case is gone.

Deliberately not changed

  • Duplicated fallback SQL: the SQL fallback pairs (PG_PRINCIPALS / _NO_SUPERUSER, PG_PRIVILEGES / _NO_ACL, MSSQL_PRINCIPALS / _NO_SERVER) stay as whole strings. Building them with string surgery would read worse (WET preference).
  • Allow-all tag: the tag's markup stays separate in the two lists. There's no shared badge component, and adding one would be a new abstraction.
  • DbPrincipal.host field: not added. name@host is how the catalog identifies a MySQL account, and a separate field would touch every query and consumer.
  • Parallel catalog queries: the principal and privilege queries stay sequential. That code predates feat(access): list roles as roles, show allow-all, one membership source #435, and running them together would open two connections at once.

Verification

  • Unit and component tests: packages/sql access + server access pass 405. Access / utilities UI pass 88. That includes a new bulk-vs-single allow-all test and an updated implied-privileges test.
  • Typechecks: cd apps/web && npx tsc --noEmit and the e2e tsc are clean. ESLint is clean on the changed files.
  • Live e2e:
    • The DB Access suite on mysql, mariadb, postgres and clickhouse passes 37/37, including the roles and allow-all test from feat(access): list roles as roles, show allow-all, one membership source #435.
    • Utilities → Database Access passes 5/5.
    • In the browser, MySQL root@% still shows the ALL PRIVILEGES (70) row, the banner and the tag, and Revoke all builds REVOKE ALL PRIVILEGES ON *.* FROM 'root'@'%';. I cancelled it rather than running it.
  • feat(access): list roles as roles, show allow-all, one membership source #435's cloud run: it was merged while "Unit tests" was still pending. That check has since passed.

🤖 Generated with Claude Code


Note

Low Risk
Refactor-only with explicit parity tests for allow-all; GRANT/REVOKE and catalog paths are reorganized but described as behaviour-preserving.

Overview
This is a readability and performance refactor of database access / allow-all handling from #435. Intended behaviour is unchanged; tests are updated accordingly.

Performance: User Management and Database Access now call findAllowAllByName once per catalog load instead of looping findAllowAll per principal. Privileges are indexed by grantee and each role’s allow-all answer is cached so large MySQL catalogs (many *.* rows) do not re-scan the full list on the UI thread.

Shared SQL layer: principalKey unifies grantee name matching for reconciliation, privilegesForPrincipal, and allow-all. reconcileDbAccess no longer takes dialect or inject SQL Server fixed-role rows; impliedFixedRolePrivileges is applied in probeDbAccess after reconcile. Membership sync uses Sets instead of repeated list scans. MySQL/MariaDB formatDbGrantee and SET DEFAULT ROLE reuse mysqlQuote / mysqlAccount / mysqlRoleRef; MySQL and ClickHouse buildGrantRevokeSql share one *.* / db.* branch. allPrivilegeTargets drops the always-null objectSchema field.

UI: DatabaseAccessModal extracts PrivilegeGroupRows, centralizes revoke confirmation in confirmRevoke, and derives membershipRole instead of syncing via an effect. UserManagement parses all MySQL-family principals as user@host on edit/drop (MariaDB bare role names unchanged).

Reviewed by Cursor Bugbot for commit 82267bb. Bugbot is set up for automated code reviews on this repo. Configure here.

No behaviour change; a /simplify pass over #435 for readability.

- Allow-all is worked out once per catalog read: findAllowAllByName indexes
  privileges by grantee and answers each role once, instead of re-reading
  the whole privilege list per account (70M comparisons for 1,000 MySQL
  accounts with *.* grants). Both lists use it.
- One name key, principalKey, shared by privilegesForPrincipal,
  reconcileDbAccess and the allow-all index; reconcile uses Sets, not list
  scans, for widely held roles.
- SQL Server's implied fixed-role rows are their own function, called
  beside reconcile, instead of hiding inside "make the catalogs agree".
- MySQL account quoting has one home: formatDbGrantee and the new
  SET DEFAULT ROLE statements use mysqlQuote / mysqlAccount / mysqlRoleRef
  from user-sql-helpers, the same helpers CREATE USER uses.
- The MySQL and ClickHouse GRANT branches shared a copied target rule; now
  one branch. allPrivilegeTargets returns its lists directly and drops the
  always-null objectSchema.
- DatabaseAccessModal: the privilege table is a file-local
  PrivilegeGroupRows; one confirmRevoke replaces three copies of the revoke
  flow; the membership role is derived instead of synced by an effect; the
  confirm field resets where the confirmation opens; granteeKindOf, which
  returned its input, is gone.
- UserManagement: any MySQL-family name parses as user@host (a MariaDB role
  has no @ and parses to itself), so the role special case goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 26, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_603ba008-3491-4ac7-8e9a-a4035b0f73fe)

@huyplb
huyplb merged commit 01c4758 into main Sep 27, 2026
12 checks passed
@huyplb
huyplb deleted the refactor/access-simplify branch September 27, 2026 01:24
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