Rules look simple, but evaluation has one important subtlety: a list is decided before any rows are read.

Two modes

Check: for get, update, delete and create, Pawabase has the record (or the input) in hand and evaluates the whole rule: true or false. Plan: for list, there are thousands of candidate rows. Pawabase first evaluates everything that doesn’t depend on a record (is the caller signed in, do they have the role, is the key a service key). Then:
  • If that already decides it, the request is allowed or refused at once, before the database is touched.
  • Otherwise, what remains is a rule about the record. If it is an equality between a column and a known value ($record.owner_id equals $auth.user_id), it becomes a database filter: WHERE owner_id = '<caller>'.
  • Anything else stays as a per-row check applied to the rows the query returns.

Why it matters

What becomes a filter

  • The owner shorthand and its string forms.
  • eq between a record column and a value that is known when the request starts ($auth.user_id, $auth.org, a literal).
  • Several such equalities combined with all.
  • All of these rules pass:
    • Is signed in: yes
    • Compare values: $record.org equals $auth.org
→ WHERE org = '<the caller's org>'.

What stays per-row

  • Comparisons other than eq between a column and a value (gt, in, starts_with…).
  • any that mixes record conditions (the database filter would need OR).
  • not around a record condition.
  • Anything comparing two columns of the same record.
These work correctly but you lose exact pages and totals. For example Any of (Owns the record (owner_id); $record.status equals published) (“mine or published”) is evaluated per row.

Keep lists fast and exact

  1. Prefer plain equality on an indexed column: owner_id, org, author_id.
  2. Split by audience when you can. Instead of one “mine or published” rule on the table, expose “mine” through the table and “published” through a second table/view or route.
  3. Index the column the rule filters on (Indexes).
  4. Design your UI for total: null: “Load more”, not “page 3 of N”.

Check mode is exact

get, update and delete see the real row, so they’re exact for any rule, including the per-row-only ones. A rule that is per-row for lists is still enforced precisely for single-record actions.

Create and update see $input

On create, the rule has no record yet, so it reads $input and $auth. On update it sees both the stored record and the new values, so you can say “owner can change a draft, and may only set status to draft or submitted”:
  • All of these rules pass:
    • Owns the record (field) owner_id
    • Compare values: $record.status equals draft
    • Compare values: $input.status is one of draft, submitted
  • expand reads each related record under its own table’s get/list rule.
  • Realtime sends a change only to people the table’s read rule allows, checked against the row as it was and as it is now. See Live records.

Performance rules of thumb

Database access

Apply to tables.

Debugging

See what decided.