Family references discriminate through the referenced row's type (+ GUC empty-string guards) #2

Open
satyabhangt wants to merge 1 commits from family-discriminates-through-the-reference into main
Owner

Intention

Make local <prop>_type columns optional denormalization instead of an engine requirement, so schemas can reference a party/entity by id alone. Came out of the payer/payee work on agreego/api (satya): invoice payer_id / bill payee_id should be the whole reference — the discriminator is derivable, and today we keep columns + domains + FKs + a stamping trigger purely to feed the queryer.

What changed

compile_one_of (queryer): a type-strategy family REFERENCE (payer, source, target…) now picks its polymorphic branch through the referenced row's own type:

CASE (SELECT __fam.type FROM agreego.entity __fam WHERE __fam.id = parent.<prop>_id)
  WHEN 'person' THEN (...)
  WHEN 'business' THEN (...)
END

instead of CASE WHEN parent.<prop>_type = 'person' THEN …. The truth, not a local cache — and the local _type column stops being required. Untouched: kind-strategy STI and a row's self-discrimination (no referenced row to consult); those still read locally. All CASEs are emitted in simple form so the operand — even as a subquery — is evaluated once.

Cost: one indexed lookup into agreego.entity per hydrated reference, on the hottest, best-cached table in the system.

Existing _type columns keep working (they're just no longer read by hydration). On the api side we'd then drop payer_type/payee_type + their domains/FKs/trigger at leisure; relationship.source_type/target_type can stay as deliberate denormalization for the SQL that reads them directly.

GUC readers (pgrx executor): auth.user_id and punc.external now NULLIF(…, '') before their COALESCE fallback. A rolled-back transaction that first-sets a custom GUC leaves it as an empty string session-wide (Postgres quirk — not unset), so the fallback never fired and the poisoned pooled connection wrote NULL created_by on every later merge. Same guard agreego.get_cue() already has. (We hit this live: the first server-side set_config('auth.user_id', …) in a rolled-back test poisoned its pool connection; reproduced in raw psql.)

Verification

cargo test: 1286 green. Golden queryer fixtures regenerated via UPDATE_EXPECT — the diff is exactly the two shapes above, no other drift.

🤖 Generated with Claude Code

## Intention Make local `<prop>_type` columns **optional denormalization instead of an engine requirement**, so schemas can reference a party/entity by id alone. Came out of the payer/payee work on `agreego/api` (satya): invoice `payer_id` / bill `payee_id` should be the whole reference — the discriminator is derivable, and today we keep columns + domains + FKs + a stamping trigger purely to feed the queryer. ## What changed **`compile_one_of` (queryer):** a type-strategy family REFERENCE (`payer`, `source`, `target`…) now picks its polymorphic branch through the referenced row's own type: ```sql CASE (SELECT __fam.type FROM agreego.entity __fam WHERE __fam.id = parent.<prop>_id) WHEN 'person' THEN (...) WHEN 'business' THEN (...) END ``` instead of `CASE WHEN parent.<prop>_type = 'person' THEN …`. The truth, not a local cache — and the local `_type` column stops being required. Untouched: `kind`-strategy STI and a row's self-discrimination (no referenced row to consult); those still read locally. All CASEs are emitted in simple form so the operand — even as a subquery — is evaluated once. Cost: one indexed lookup into `agreego.entity` per hydrated reference, on the hottest, best-cached table in the system. Existing `_type` columns keep working (they're just no longer read by hydration). On the api side we'd then drop `payer_type`/`payee_type` + their domains/FKs/trigger at leisure; `relationship.source_type`/`target_type` can stay as deliberate denormalization for the SQL that reads them directly. **GUC readers (pgrx executor):** `auth.user_id` and `punc.external` now `NULLIF(…, '')` before their COALESCE fallback. A rolled-back transaction that *first-sets* a custom GUC leaves it as an **empty string** session-wide (Postgres quirk — not unset), so the fallback never fired and the poisoned pooled connection wrote NULL `created_by` on every later merge. Same guard `agreego.get_cue()` already has. (We hit this live: the first server-side `set_config('auth.user_id', …)` in a rolled-back test poisoned its pool connection; reproduced in raw psql.) ## Verification `cargo test`: 1286 green. Golden queryer fixtures regenerated via `UPDATE_EXPECT` — the diff is exactly the two shapes above, no other drift. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
satyabhangt added 1 commit 2026-08-25 22:22:10 +00:00
Intention: make local <prop>_type columns optional denormalization instead
of an engine requirement — writers should only ever have to say WHO.

Outcomes:
- compile_one_of: a type-strategy family REFERENCE (payer, source, target)
  now selects its polymorphic branch via the referenced row's own type —
  CASE (SELECT type FROM agreego.entity WHERE id = <alias>.<prop>_id) —
  the truth itself, instead of requiring a <prop>_type column beside the
  id. kind-strategy STI and a row's self-discrimination still read locally
  (there is no referenced row to consult). All CASEs are now simple-form,
  so the operand is evaluated once even as a subquery.
- GUC readers guard against the empty string: a rolled-back transaction
  that FIRST-sets a custom GUC leaves it '' session-wide (not unset), and
  COALESCE alone never fires — auth.user_id and punc.external now NULLIF
  first, matching agreego.get_cue's own convention.
- Golden fixtures regenerated via UPDATE_EXPECT; 1286 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This pull request has changes conflicting with the target branch.
  • src/database/executors/pgrx.rs
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin family-discriminates-through-the-reference:family-discriminates-through-the-reference
git checkout family-discriminates-through-the-reference
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: cellular/jspg#2
No description provided.