MemberJunction / MemberJunction/MJ

PostgreSQL: CodeGen silently skips layered base views instead of throwing, so #3477's loud stop never fires

Open
#3,837 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
1d 8h
Merged PRs (30d)
308

Description

## One sentence

On PostgreSQL, CodeGen **silently skips** the base view of an entity with `GeneratedBaseViewName` set — it emits that entity's CRUD functions and its view *permissions* file but no view at all, reports success, and never reaches `assertLayeredBaseViewSupported()` — so the deliberate hard stop described in #3477 does not fire on the ordinary `mj codegen` path.

## Why this matters

#3477 establishes that layered base views are SQL-Server-only and that the PG provider throws by design, and explicitly calls that *good*: "The failure is loud rather than silent." This issue reports that on the incremental codegen path it is **not** loud. The install is left in the exact state layering was meant to prevent:

- the inner `…Generated` view is never created,
- the outer application-owned `BaseView` (which selects from it) has nothing to wrap,
- `mj codegen` exits reporting success,
- and nothing anywhere says PostgreSQL declined to do the work.

A guard that cannot be reached provides no protection, and the "loud failure" property #3477 relies on is currently not there to rely on.

## Environment

- `@memberjunction/cli@6.1.0-edge.2`, `@memberjunction/codegen-lib@6.1.0-edge.2` (published npm build, not a local tree)
- PostgreSQL 17.10, fresh DB migrated with `mj migrate --tag v6.1.0-edge.2`
- App under test: `MemberJunction/bizapps-common` @ `next`, schema `__mj_bizappscommon`

Both halves of the intended mechanism are present in the published build:

```
dist/Database/sql_codegen.js
generatesView = (entity.BaseViewGenerated || entity.HasLayeredBaseView) && !entity.VirtualEntity
dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js
assertLayeredBaseViewSupported (2 occurrences)
```

## Repro

1. Fresh PG database; `mj migrate --tag v6.1.0-edge.2`; install `bizapps-common`'s `migrations-pg/`.
2. Apply a migration that flips two entities to layered, i.e. sets `BaseViewGenerated = false` and `GeneratedBaseViewName`:

```sql
SELECT "Name","BaseViewGenerated","GeneratedBaseViewName"
FROM __mj."Entity" WHERE "GeneratedBaseViewName" <> '';

MJ_BizApps_Common: People | f | vwPeopleGenerated
MJ_BizApps_Common: Organizations | f | vwOrganizationsGenerated
```

3. Run `mj codegen` with `DB_PLATFORM=postgresql`.

**Observed:** completes — `MJ CodeGen complete — 385 entities in 60.93s`. No exception, and no occurrence of `layered base views are not supported` anywhere in the output.

The generated-SQL directory shows the skip precisely. Same schema, same run, layered vs non-layered:

| Entity | layered | `vw*.view.generated.sql` | `vw*.view.permissions.generated.sql` |
|---|---|---|---|
| `People` | yes | **missing** | present |
| `Organizations` | yes | **missing** | present |
| `OrganizationTypes` | no | present | present |
| `ContactMethods` | no | present | present |
| `Addresses` | no | present | present |

No emitted file anywhere references `vwPeopleGenerated` or `vwOrganizationsGenerated`, and neither view exists in the database afterwards. CRUD functions (`spCreatePerson`, `spUpdateOrganization`, …) and the trigger functions *were* generated normally, so the entities were unquestionably in scope and processed — only the view was dropped.

**Expected:** either the documented throw from `assertLayeredBaseViewSupported()`, or — if skipping is the intended behaviour on this path — a loud, non-zero-exit diagnostic naming the entities.

## Note on the permissions file

The permissions artifact being emitted for a view that was never created is worth a look on its own: it means the run produced `GRANT` statements against a relation it knew it had not generated.

## Downstream impact

Found while producing PostgreSQL counterparts for `bizapps-common`, whose two newest migrations (2026-08-13/14) adopt layering. On PostgreSQL those migrations produce metadata that says "layered" and a database with no inner view, and the follow-up migration that creates the outer wrapper has nothing to select from. Because codegen reports success, the resulting install looks healthy.

The same `GeneratedBaseViewName` metadata ships to every install on two core entities per #3477, so any install where those are in codegen scope is exposed to the same silent path.

## Related

- #3477 — layered base views are SQL-Server-only and PG CodeGen throws on them (this issue: the throw is not reached on the incremental path)
- #3514 — SS artifacts and the PG bake regenerating from different metadata states

🤖 Filed with [Claude Code](https://claude.com/claude-code)

Contributor guide

Open the contributing guide

Research direction

Start with dist/Database/sql_codegen.js and the mj codegen entry point, then trace the generatesView condition for entities with GeneratedBaseViewName. Compare that path with assertLayeredBaseViewSupported in dist/Database/providers/postgresql/PostgreSQLCodeGenProvider.js. Done means layered PostgreSQL entities no longer silently skip their views and the run either throws or exits nonzero with a diagnostic naming them.

Written by the indexing model from the issue text.

Assessment

Tech stack
node.js, postgresql
Domain
databases, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.