Skip to content

ALTER ENTITIES — the bulk ADD ATTRIBUTE form - #1136

Merged
ako merged 1 commit into
mainfrom
feat/alter-entities-bulk
Sep 18, 2026
Merged

ako merged 1 commit into
mainfrom
feat/alter-entities-bulk

Conversation

@ako

@ako ako commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Giving every entity in a module an audit trail cost one statement per entity. Measured on a real module: 10 statements, ~242 tokens, against one statement at ~25 — and at 200 entities the single-entity form is ~9,700 tokens of near-identical text for an agent to emit and a human to review.

alter entities in Sales
  add attribute if not exists CreatedDate: AutoCreatedDate,
  add attribute if not exists ChangedDate: AutoChangedDate
  where persistent;

Design

Scope is deliberately narrow, following ALTER PAGES, which is bulk for exactly one operation. ADD ATTRIBUTE onlyDROP and RENAME aimed at a set are destructive by a typo, and SET POSITION on every entity is meaningless. WHERE reuses the persistence words CREATE ENTITY already uses rather than inventing a predicate language, so no new lexer token was needed.

The executor resolves the target set and then runs each action through execAlterEntity unchanged. That delegation is the design: the single-entity path already carries the reserved-word refusals, access-rule reconciliation, the IF NOT EXISTS skip and write elision, and a second implementation would have to be kept in step with all of it.

Three exclusions — mxbuild taught me two of them

The first version passed every unit test and produced a project with 12 errors:

  • A view entity is never a target, with or without a filter. Its columns come from its OQL select list, so an added attribute is CE6770 "View Entity is out of sync with the OQL Query" — 10 of the 12.
  • A specialization whose ancestor is also a target is skipped. The same name on a generalization and its child is CE0069 "Duplicate member name" — the other 2, on DmTest.Vehicle/Truck/PassengerCar. The parent is kept and the child inherits the member, which is what the author wanted anyway.
  • An unscoped sweep skips System and every Marketplace module, and reports which. An upgrade replaces those modules and takes the attribute with it, so the write would be silently undone later rather than refused now. Naming a module with IN is taken as meaning it — the same division mxcli layout makes.

The first two are the interesting ones: they are not expressible as grammar or as a check on the statement, only as knowledge about what Mendix will accept. Unit tests could not have found them, and did not.

Verification

On a real 11.14 project:

result
one statement, module-scoped all 5 persistent entities get both audit members
re-run each reported already present — idempotent
unscoped sweep skips 8 System/Marketplace modules, touches only the 2 user ones

TestMxCheck_DoctypeScripts/01-domain-model passes at 0 errors, having failed at 12 before the exclusions. The doctype script now exercises all three filter forms. Whole mdl/executor integration package: 0 failures, 200s. make lint-go and make test pass.

Tests at both layers: 8 executor tests (filters, view exclusion, inheritance skip, marketplace guard, unknown module) and 4 parser tests, including one that the single-entity form still parses.

The registry handler-count snapshot needed updating — that guard working as intended.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BNDe35kDNsMX5cz4Ahn4rk

Giving every entity in a module an audit trail cost one statement per
entity. Measured on a real module: 10 statements, ~242 tokens, against one
statement at ~25 -- and at 200 entities the single-entity form is ~9,700
tokens of near-identical text for an agent to emit and a human to review.

  alter entities in Sales
    add attribute if not exists CreatedDate: AutoCreatedDate,
    add attribute if not exists ChangedDate: AutoChangedDate
    where persistent;

Scope is deliberately narrow, following ALTER PAGES, which is bulk for
exactly one operation. ADD ATTRIBUTE only: DROP and RENAME aimed at a set
are destructive by a typo, and SET POSITION on every entity is meaningless.
WHERE reuses the persistence words CREATE ENTITY already uses rather than
inventing a predicate language, so no new lexer token was needed.

The executor resolves the target set and then runs each action through
execAlterEntity UNCHANGED. That delegation is the design: the single-entity
path already carries the reserved-word refusals, access-rule reconciliation,
the IF NOT EXISTS skip and write elision, and a second implementation would
have to be kept in step with all of it.

Three exclusions, and mxbuild taught me two of them -- the first version of
this passed every unit test and produced a project with 12 errors:

  - A VIEW entity is never a target, with or without a filter. Its columns
    come from its OQL select list, so an added attribute is CE6770 "View
    Entity is out of sync with the OQL Query" (10 of the 12).
  - A SPECIALIZATION whose ancestor is also a target is skipped: the same
    name on a generalization and its child is CE0069 "Duplicate member
    name" (the other 2, on DmTest.Vehicle/Truck/PassengerCar). The parent is
    kept and the child inherits the member, which is what the author wanted.
  - An UNSCOPED sweep skips System and every Marketplace module, and reports
    which. An upgrade replaces those modules and takes the attribute with
    it, so the write would be silently undone later rather than refused now.
    Naming a module with IN is taken as meaning it, the same division
    `mxcli layout` makes.

Verified on a real 11.14 project: one statement gives all 5 persistent
entities in a module both audit members, a re-run reports each as already
present, and an unscoped sweep skips 8 System/Marketplace modules and
touches only the 2 user ones. mxbuild is clean -- the doctype script now
exercises all three filter forms and `TestMxCheck_DoctypeScripts` passes at
0 errors, having failed at 12 before the exclusions.

Full stack per the checklist: grammar, AST, visitor, executor, syntax topic,
quick reference, doctype example, and tests at the parser and executor
layers. The registry handler-count snapshot is updated, which is the guard
working as intended.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNDe35kDNsMX5cz4Ahn4rk
@github-actions

Copy link
Copy Markdown

AI Code Review

Summary

The PR adds bulk ALTER ENTITIES syntax for adding attributes to multiple entities at once, following the same pattern as ALTER PAGES. It's a well-designed, focused feature that reuses existing infrastructure properly.

What Looks Good

  • Correct reuse of existing paths: The executor delegates to execAlterEntity unchanged, inheriting all the single-entity logic (reserved word checks, IF NOT EXISTS, write elision, etc.)
  • Thorough exclusions: Properly skips view entities (CE6770), specialization inheritance conflicts (CE0069), and System/Marketplace modules in unscoped sweeps
  • Complete test coverage:
    • Executor tests cover all filter combinations, exclusions, and error cases
    • Visitor tests validate AST construction
    • Doctest examples show real usage
    • Registry updates ensure the statement is recognized
  • Clean implementation:
    • Minimal new code (just the bulk handler and resolver)
    • No duplication - leverages existing execAlterEntity
    • Proper error context wrapping
    • Deterministic sorted output for diffability
  • Documentation updated:
    • Syntax reference and quick reference
    • Doctest examples with clear comments
    • Help text shows the new bulk form alongside existing alter entity options

Recommendation

Approve. The PR fully addresses the checklist requirements:

  • Overlap & duplication: No conflicts with existing features
  • MDL syntax design: Follows CREATE/ALTER/DROP/SHOW/DESCRIBE pattern, reads as English, uses qualified names, proper property format
  • Full-stack consistency: Grammar → AST → Visitor → Executor → Backend (no LSP/DESCRIBE needed as it doesn't create new artifact types)
  • Test coverage: New executor command has MDL examples, integration paths tested
  • Security/robustness: No silent side effects, proper error handling, correct method receivers
  • Scope/atomicity: Single focused feature (bulk ADD ATTRIBUTE only)
  • Code quality: Consistent application of patterns, no maintenance risks introduced
  • Bugs/correctness: Exclusions based on real mxbuild errors, verified with tests

The feature solves the stated problem (reducing boilerplate for audit trails) while maintaining all existing safety guarantees. The implementation is minimal, correct, and well-tested.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako
ako merged commit 0a30ab2 into main Sep 18, 2026
13 checks passed
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