feat: expose row-level can_edit/can_delete on select *

Compute per-row editability and deletability from a table's row-level
security policies and return them as synthetic columns so clients can
hide edit/delete affordances for rows the user cannot change.

- Introspect pg_policies and relrowsecurity at schema-cache load and
  combine the UPDATE/DELETE USING qualifiers per table (permissive OR,
  restrictive AND).
- Store the combined qualifiers on Table and inject can_edit/can_delete
  as computed select fields when expanding `select *`, only for
  RLS-enabled tables with a matching policy (COALESCE'd to a boolean).
- Keep the computed columns out of the OpenAPI spec so they are not
  rendered as regular fields.
- Add a cfExpression field to CoercibleField to carry raw SQL
  expressions through the planner to SqlFragment.
This commit is contained in:
2026-08-29 11:30:21 +02:00
parent 77ab8f83ac
commit 7989108b0b
11 changed files with 329 additions and 14 deletions
@@ -7,6 +7,42 @@
tableIsView: false
tableName: authors_only
tablePKCols: []
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
- - qiName: no_rls_items
qiSchema: public
- tableColumns:
id:
colDefault: null
colDescription: null
colEnum: []
colMaxLen: null
colName: id
colNominalType: integer
colNullable: false
colType: integer
name:
colDefault: null
colDescription: null
colEnum: []
colMaxLen: null
colName: name
colNominalType: text
colNullable: true
colType: text
tableDeletable: true
tableDescription: null
tableInsertable: true
tableIsView: false
tableName: no_rls_items
tablePKCols:
- id
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -39,6 +75,53 @@
tableName: cats
tablePKCols:
- id
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
- - qiName: rls_items
qiSchema: public
- tableColumns:
account_id:
colDefault: null
colDescription: null
colEnum: []
colMaxLen: null
colName: account_id
colNominalType: bigint
colNullable: true
colType: bigint
id:
colDefault: null
colDescription: null
colEnum: []
colMaxLen: null
colName: id
colNominalType: integer
colNullable: false
colType: integer
name:
colDefault: null
colDescription: null
colEnum: []
colMaxLen: null
colName: name
colNominalType: text
colNullable: true
colType: text
tableDeletable: true
tableDescription: null
tableInsertable: true
tableIsView: false
tableName: rls_items
tablePKCols:
- id
tableRlsDeleteQual: COALESCE(((((current_setting('request.jwt.claims'::text, true))::json
->> 'account_id'::text))::bigint = account_id), false)
tableRlsEditQual: COALESCE(((((current_setting('request.jwt.claims'::text, true))::json
->> 'account_id'::text))::bigint = account_id), false)
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -70,6 +153,8 @@
tableIsView: true
tableName: items_w_isolation_level
tablePKCols: []
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -102,6 +187,8 @@
tableName: directors
tablePKCols:
- id
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -115,6 +202,8 @@
tableIsView: false
tableName: projects
tablePKCols: []
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -128,6 +217,8 @@
tableIsView: true
tableName: infinite_recursion
tablePKCols: []
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: false
@@ -187,6 +278,8 @@
tableName: awards
tablePKCols:
- id
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -228,6 +321,8 @@
tableName: films
tablePKCols:
- id
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true
@@ -250,6 +345,8 @@
tableIsView: false
tableName: items
tablePKCols: []
tableRlsDeleteQual: null
tableRlsEditQual: null
tableSchema: public
tableUniqueCols: []
tableUpdatable: true