Skip to content

Commit 547ce4a

Browse files
akoclaude
andcommitted
docs: add RENAME with reference refactoring proposal
Design for safe RENAME commands that automatically update all BY_NAME references across the project. Phased approach: generic BSON string scanner with dry run first, then entity/enum rename, microflow/page rename, and finally module rename. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 13f28bb commit 547ce4a

File tree

1 file changed

+243
-0
lines changed

1 file changed

+243
-0
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Proposal: RENAME with Reference Refactoring
2+
3+
**Status:** Draft
4+
**Date:** 2026-04-08
5+
6+
## Motivation
7+
8+
Renaming entities, microflows, pages, and modules is one of the most common refactoring operations. Currently mxcli has `RENAME ENTITY` and `RENAME MODULE` in the grammar but neither is implemented. Renaming in Mendix is dangerous because cross-references are stored as qualified name strings (BY_NAME_REFERENCE) — renaming without updating references breaks the project.
9+
10+
A safe RENAME command that automatically updates all references would be a significant productivity gain, especially for AI-assisted refactoring workflows (e.g., monolith-to-multi-app decomposition).
11+
12+
## Current State
13+
14+
### What exists
15+
16+
| Operation | Status |
17+
|-----------|--------|
18+
| `ALTER ENTITY ... RENAME ATTRIBUTE Old TO New` | Works |
19+
| `ALTER ENUMERATION ... RENAME VALUE Old TO New` | Works |
20+
| `RENAME ENTITY Module.Old TO New` | Grammar only, not implemented |
21+
| `RENAME MODULE Old TO New` | Grammar only, not implemented |
22+
| `RENAME MICROFLOW/PAGE/NANOFLOW ...` | Not in grammar |
23+
24+
### Existing reference-update code
25+
26+
The codebase already has precedent for scanning and rewriting BY_NAME references:
27+
28+
- `UpdateEnumerationRefsInAllDomainModels()` in `writer_domainmodel.go` — scans all entities to update enumeration qualified names when an enum moves between modules
29+
- `MoveEntity()` in `writer_domainmodel.go` — updates validation rule attribute references when an entity is moved
30+
31+
### How references are stored
32+
33+
Mendix MPR files use two reference types:
34+
35+
| Type | Storage | Example | Rename Impact |
36+
|------|---------|---------|---------------|
37+
| BY_ID_REFERENCE | Binary UUID | Association parent/child pointers, index attribute pointers | **Safe** — IDs survive rename |
38+
| BY_NAME_REFERENCE | Qualified name string | `"Module.EntityName"`, `"Module.MicroflowName"` | **Breaks** — string must be updated |
39+
40+
All cross-document references use BY_NAME_REFERENCE. This is the fundamental challenge.
41+
42+
## Design
43+
44+
### Syntax
45+
46+
```sql
47+
-- Rename entity (updates all references)
48+
RENAME ENTITY Module.OldName TO NewName;
49+
50+
-- Rename microflow
51+
RENAME MICROFLOW Module.OldName TO NewName;
52+
53+
-- Rename nanoflow
54+
RENAME NANOFLOW Module.OldName TO NewName;
55+
56+
-- Rename page
57+
RENAME PAGE Module.OldName TO NewName;
58+
59+
-- Rename module (updates ALL qualified names)
60+
RENAME MODULE OldName TO NewName;
61+
62+
-- Dry run: show what would change without modifying anything
63+
RENAME ENTITY Module.OldName TO NewName DRY RUN;
64+
```
65+
66+
The `TO` target is always just a name (not qualified) — the document stays in the same module. Cross-module moves are handled by the existing `MOVE` command.
67+
68+
### Reference Update Matrix
69+
70+
When renaming `Module.OldEntity` to `Module.NewEntity`, these BY_NAME references must be updated:
71+
72+
| Location | Reference Field | Example |
73+
|----------|----------------|---------|
74+
| **Microflow actions** | Entity in CREATE/RETRIEVE/DELETE/CHANGE/AGGREGATE/LIST | `"Module.OldEntity"``"Module.NewEntity"` |
75+
| **Microflow parameters** | Entity type reference | `$Param: Module.OldEntity` |
76+
| **Microflow return types** | Entity type reference | `RETURNS Module.OldEntity` |
77+
| **Associations** | Cross-module child/parent refs | `"Module.OldEntity"` in `Child` field |
78+
| **Generalization** | Parent entity reference | `EXTENDS Module.OldEntity` |
79+
| **Enumeration attributes** | EnumerationRef on attribute types | `"Module.OldEnum"` |
80+
| **View entity OQL** | Entity names in SELECT/FROM/JOIN | `FROM Module.OldEntity AS e` |
81+
| **Page datasources** | Entity in DATABASE/XPATH sources | `DataSource: DATABASE Module.OldEntity` |
82+
| **Page parameter types** | Entity type in page params | `Params: { $p: Module.OldEntity }` |
83+
| **Navigation** | Home page, login page refs | `"Module.OldPage"` |
84+
| **Settings** | After-startup, before-shutdown microflow | `"Module.OldMicroflow"` |
85+
| **Security** | Allowed module roles on microflows | (BY_NAME refs) |
86+
| **Member access rules** | Attribute/association names | Column names in access rules |
87+
| **Import/Export mappings** | Entity references in element mappings | `"Module.OldEntity"` |
88+
| **Business events** | Entity references in messages | `"Module.OldEntity"` |
89+
| **Java action parameters** | Entity type references | `"Module.OldEntity"` |
90+
| **Scheduled events** | Microflow references | `"Module.OldMicroflow"` |
91+
92+
### Architecture: Unified Reference Scanner
93+
94+
Instead of writing one-off update functions per document type, implement a **generic reference scanner** that:
95+
96+
1. Reads every document in the project (all units from the MPR)
97+
2. Walks the BSON tree looking for string values matching the old qualified name
98+
3. Replaces matching strings with the new qualified name
99+
4. Rewrites the modified documents
100+
101+
This brute-force approach is safe because:
102+
- BY_NAME references are always stored as plain strings in BSON
103+
- The qualified name format (`Module.Name`) is unambiguous — no false positives from substring matches
104+
- It catches references we haven't explicitly listed (future-proof)
105+
106+
```go
107+
// RenameReferences scans all documents and replaces qualified name strings.
108+
func (w *Writer) RenameReferences(oldQualifiedName, newQualifiedName string) (int, error) {
109+
units, err := w.reader.ListRawUnits()
110+
if err != nil {
111+
return 0, err
112+
}
113+
114+
count := 0
115+
for _, unit := range units {
116+
raw, err := bson.Unmarshal(unit.Contents)
117+
if err != nil {
118+
continue
119+
}
120+
if updated, changed := replaceStringValues(raw, oldQualifiedName, newQualifiedName); changed {
121+
contents, _ := bson.Marshal(updated)
122+
w.updateUnitContents(unit.ID, contents)
123+
count++
124+
}
125+
}
126+
return count, nil
127+
}
128+
```
129+
130+
The `replaceStringValues` function recursively walks the BSON document tree and replaces exact string matches. It also handles partial matches for attribute-qualified names (e.g., `Module.Entity.Attribute` when renaming `Module.Entity`).
131+
132+
### Matching Rules
133+
134+
For a rename of `Module.OldName` to `Module.NewName`:
135+
136+
| Pattern | Match? | Example |
137+
|---------|--------|---------|
138+
| Exact: `"Module.OldName"` | Yes | Entity ref in microflow action |
139+
| Prefix: `"Module.OldName.Attribute"` | Yes → `"Module.NewName.Attribute"` | Validation rule attribute ref |
140+
| Substring: `"SomeModule.OldName"` | No | Different module, different entity |
141+
| Substring: `"Module.OldNameExtra"` | No | Longer name, not a match |
142+
143+
The match is: string equals `oldName` OR string starts with `oldName + "."`.
144+
145+
### Dry Run Mode
146+
147+
`DRY RUN` scans without modifying, outputting what would change:
148+
149+
```
150+
RENAME ENTITY MyModule.Customer TO Client DRY RUN;
151+
152+
Would rename: MyModule.Customer → MyModule.Client
153+
References found: 23
154+
MyModule.ACT_Customer_Save (Microflow) — 3 references
155+
MyModule.ACT_Customer_Delete (Microflow) — 2 references
156+
MyModule.Customer_Overview (Page) — 4 references
157+
MyModule.Customer_Edit (Page) — 5 references
158+
MyModule.Order (Entity) — 1 reference (association)
159+
MyModule.IMM_CustomerResponse (Import Mapping) — 2 references
160+
...
161+
```
162+
163+
This lets users preview the blast radius before committing.
164+
165+
### Module Rename
166+
167+
Module rename is the most impactful — every qualified name starting with `OldModule.` must change. The same scanner works, but matches on the prefix `OldModule.` and replaces with `NewModule.`.
168+
169+
Additionally, module rename must update:
170+
- The module document's `Name` field
171+
- The `themesource/oldmodule/` directory (rename to `themesource/newmodule/`)
172+
- The `javasource/oldmodule/` directory
173+
- Folder container names in the hierarchy
174+
175+
## Implementation Plan
176+
177+
### Phase 1: Reference scanner + dry run
178+
179+
1. Implement `RenameReferences(old, new string) ([]RenameHit, error)` in `sdk/mpr/writer_rename.go`
180+
2. Implement `replaceStringValues()` BSON tree walker
181+
3. Add `DRY RUN` support to show affected documents without modifying
182+
4. Wire `RENAME ENTITY` and `RENAME MODULE` grammar rules to AST + visitor + executor
183+
5. Executor calls dry-run scanner and reports results
184+
185+
**Deliverable**: `RENAME ENTITY Module.Old TO New DRY RUN;` works and lists all references.
186+
187+
### Phase 2: Entity and enumeration rename
188+
189+
6. Implement actual rename: update entity Name + run reference scanner
190+
7. Handle attribute-qualified names (`Module.Entity.Attribute` patterns)
191+
8. Handle OQL queries in ViewEntitySourceDocuments (string replacement in OQL text)
192+
9. Test with roundtrip: rename → `mx check` → 0 errors
193+
10. Add `RENAME ENUMERATION` support
194+
195+
### Phase 3: Microflow, nanoflow, page rename
196+
197+
11. Implement `RENAME MICROFLOW/NANOFLOW/PAGE` in grammar + executor
198+
12. Update navigation references (home pages, login pages)
199+
13. Update settings references (after-startup, before-shutdown)
200+
14. Update scheduled event microflow references
201+
202+
### Phase 4: Module rename
203+
204+
15. Implement `RENAME MODULE` — prefix replacement on all qualified names
205+
16. Handle filesystem directories (themesource, javasource)
206+
17. Handle the module security document name
207+
18. Handle folder container hierarchy updates
208+
209+
### Phase 5: Association and constant rename
210+
211+
19. `RENAME ASSOCIATION Module.Old TO New`
212+
20. `RENAME CONSTANT Module.Old TO New`
213+
214+
## Risks
215+
216+
### False positives in string replacement
217+
218+
The brute-force scanner replaces any BSON string matching the qualified name. In theory, a string literal in a microflow expression could contain `"Module.EntityName"` as user-visible text. This is extremely unlikely (qualified names are an internal format) but possible.
219+
220+
**Mitigation**: The dry-run mode lets users review before applying. We could also skip known text-only fields (Documentation, Caption) if false positives become an issue.
221+
222+
### OQL query rewriting
223+
224+
OQL queries reference entities as `Module.Entity` in FROM/JOIN clauses. Simple string replacement works for entity renames, but module renames need care — the OQL alias (`AS e`) stays the same, only the qualified name changes.
225+
226+
### Java source files
227+
228+
Renaming an entity changes the proxy class name in `javasource/<module>/proxies/`. Java source files that reference the old class name will break. This is out of scope for Phase 1-3 but should be documented as a limitation.
229+
230+
## Scope Exclusions
231+
232+
- **Java source file updates**: Out of scope — rename produces correct MPR but Java files need manual update
233+
- **Widget property string references**: Pluggable widget properties may contain entity/attribute names as strings — these are not updatable without widget-specific knowledge
234+
- **Git history**: Rename doesn't create a git rename operation — it modifies files in place
235+
- **Cross-project references**: Only single-project rename (multi-project is deferred to multi-app support)
236+
237+
## Effort Estimate
238+
239+
- Phase 1 (scanner + dry run): Small — ~150 lines Go
240+
- Phase 2 (entity rename): Medium — ~100 lines Go + tests
241+
- Phase 3 (microflow/page rename): Small — reuses Phase 2 scanner
242+
- Phase 4 (module rename): Medium — filesystem + prefix replacement
243+
- Phase 5 (association/constant): Small — reuses scanner

0 commit comments

Comments
 (0)