migrate-oracle-forms · git:20260824.826e4cd · 2026-08-24 · sha256 e6c081e0b10bbb1c
migrate-oracle-forms git:20260824.826e4cdA
Immutable. This exact content is served forever at /api/v1/blob/e6c081e0b10bbb1c.
---
name: migrate-oracle-forms
description: "Assess and migrate Oracle Forms applications to Mendix — .fmb forms to pages, PL/SQL to microflows, and a staged migration strategy. Use when analysing or converting an Oracle Forms system."
---
# Oracle Forms to Mendix Migration Skill
This skill provides comprehensive guidance for migrating Oracle Forms applications to Mendix using MDL (Mendix Definition Language).
## When to Use This Skill
Use this skill when:
- Converting Oracle Forms (.fmb) applications to Mendix
- Translating PL/SQL logic to Mendix microflows
- Mapping Oracle Forms UI elements to Mendix widgets
- Planning a migration strategy for legacy Oracle Forms systems
## Migration Overview
Oracle Forms migration to Mendix involves:
1. **Data Model**: Oracle tables → Mendix entities
2. **Business Logic**: PL/SQL triggers/procedures → Mendix microflows
3. **User Interface**: Forms blocks/items → Mendix pages/widgets
4. **Navigation**: Form canvases → Mendix page navigation
## Reserved Word Conflicts
Most common words (`check`, `text`, `format`, `value`, `type`, `index`, `status`, `select`, etc.) now work **unquoted** as attribute names in MDL. Only structural keywords (`create`, `delete`, `begin`, `end`, `return`, `entity`, `module`) need quoting.
### Naming Best Practices
While most words are no longer reserved, using descriptive names is still recommended for clarity:
| Oracle Forms Field | Recommended Mendix Name | Notes |
|-------------------|-------------------------|-------|
| `check` | `check` or `CheckStatus` | Works unquoted |
| `text` | `text` or `TextContent` | Works unquoted |
| `format` | `format` or `FormatType` | Works unquoted |
| `value` | `value` or `FieldValue` | Works unquoted |
| `Name` | `Name` or `ItemName` | Works unquoted (not a keyword) |
| `type` | `type` or `ItemType` | Works unquoted |
| `create` | `"create"` or `CreatedBy` | **Requires quoting** (structural keyword) |
| `delete` | `"delete"` or `IsDeleted` | **Requires quoting** (structural keyword) |
### Example
```mdl
create persistent entity MyModule.FormField (
check: boolean default false, -- Works unquoted
text: string(500), -- Works unquoted
format: string(50), -- Works unquoted
CheckFlag: boolean default false, -- Renamed alternative (also fine)
TextContent: string(500), -- Renamed
FormatType: string(50) -- Renamed
);
```
## Script Organization
### Execution Order Dependencies
MDL scripts execute statements sequentially. Items created in one statement can be referenced in subsequent statements within the **same script execution**.
**Key Insight**: Microflows and pages created earlier in the script are tracked and can be resolved by later statements.
### Recommended Script Structure
```mdl
-- check-skip: the PHASE 3 page block uses shorthand pseudo-syntax
-- (layout/title/parameter/widgets, dataview source, INPUT ...) to sketch the
-- migrated UI concept; it is not runnable MDL. See create-page / overview-pages.
-- ============================================
-- PHASE 1: Domain Model (Entities & Associations)
-- ============================================
create persistent entity MyModule.Customer (
CustomerCode: string(50),
CustomerName: string(200),
Email: string(200),
IsActive: boolean default true
);
create persistent entity MyModule.Order (
OrderNumber: string(50),
OrderDate: datetime,
TotalAmount: decimal
);
create association MyModule.Order_Customer
from MyModule.Order to MyModule.Customer
type reference;
/
-- ============================================
-- PHASE 2: Microflows (Business Logic)
-- ============================================
/**
* Validates and saves a customer record
* Replaces Oracle Forms POST-INSERT/POST-UPDATE triggers
*/
create microflow MyModule.ACT_Customer_Save ($Customer: MyModule.Customer)
returns boolean as $success
begin
declare $success boolean = false;
-- Validation (replaces WHEN-VALIDATE-ITEM)
if $Customer/CustomerCode = empty then
validation feedback $Customer/CustomerCode message 'Customer code is required';
return false;
end if;
commit $Customer;
set $success = true;
return $success;
end;
/
-- ============================================
-- PHASE 3: Pages (User Interface)
-- ============================================
-- Now this page can reference the microflow created above
create page MyModule.Customer_Edit
layout Atlas_Default
title 'Edit Customer'
parameter $Customer: MyModule.Customer
widgets (
dataview source $Customer (
INPUT 'CustomerCode' attribute CustomerCode label 'Customer Code',
INPUT 'CustomerName' attribute CustomerName label 'Name',
INPUT 'Email' attribute Email label 'Email',
container 'ButtonBar' (
-- Reference to microflow created in Phase 2
button 'Save' call microflow MyModule.ACT_Customer_Save (
Customer = $Customer
),
button 'Cancel' on CLICK close page
)
)
);
/
```
## Validation Feedback
### VALIDATION FEEDBACK Syntax
**CRITICAL**: VALIDATION FEEDBACK requires an attribute path, not just a message.
**WRONG:**
```mdl
validation feedback 'Customer code is required'; -- Missing attribute!
```
**CORRECT:**
```mdl
-- Syntax: VALIDATION FEEDBACK $entity/attribute MESSAGE 'message'
validation feedback $Customer/CustomerCode message 'Customer code is required';
validation feedback $Order/OrderDate message 'Order date cannot be in the future';
```
### Mapping Oracle Forms Validation
| Oracle Forms | Mendix MDL |
|--------------|------------|
| `when-VALIDATE-item` trigger | `if ... validation feedback` in microflow |
| `raise FORM_TRIGGER_FAILURE` | `validation feedback` + `return false` |
| `message('error text')` | `validation feedback $entity/attribute message 'error text'` |
### Complete Validation Pattern
```mdl
/**
* Validates order before save
* Replaces Oracle Forms WHEN-VALIDATE-RECORD trigger
*/
create microflow MyModule.ACT_Order_Validate ($Order: MyModule.Order)
returns boolean as $IsValid
begin
declare $IsValid boolean = true;
-- Required field validation
if $Order/OrderNumber = empty then
validation feedback $Order/OrderNumber message 'Order number is required';
set $IsValid = false;
end if;
-- Date validation
if $Order/OrderDate > [%CurrentDateTime%] then
validation feedback $Order/OrderDate message 'Order date cannot be in the future';
set $IsValid = false;
end if;
-- Cross-field validation
if $Order/TotalAmount < 0 then
validation feedback $Order/TotalAmount message 'Total amount cannot be negative';
set $IsValid = false;
end if;
return $IsValid;
end;
/
```
## PL/SQL to Microflow Mapping
### Data Manipulation
| Oracle PL/SQL | Mendix MDL |
|---------------|------------|
| `insert into table ...` | `$var = create Module.Entity (...)` |
| `update table set ...` | `change $var (...)` + `commit $var` |
| `delete from table ...` | `delete $var` |
| `select ... into ...` | `retrieve $var from Module.Entity where ...` |
| `commit` | `commit $var` |
| `rollback` | Built-in with error handlers |
### Control Flow
| Oracle PL/SQL | Mendix MDL |
|---------------|------------|
| `if ... then ... elsif ... else ... end if` | `if ... then ... else ... end if` |
| `for ... loop ... end loop` | `loop $item in $list begin ... end loop` |
| `while ... loop ... end loop` | Not directly supported; use recursive microflow |
| `CURSOR` | `retrieve $list from ...` then `loop` |
| `EXCEPTION when ... then` | `on error { ... }` |
### Example: PL/SQL to MDL
**Oracle PL/SQL:**
```sql
declare
v_count NUMBER := 0;
v_total NUMBER := 0;
begin
for rec in (select * from orders where status = 'PENDING') loop
v_count := v_count + 1;
v_total := v_total + rec.amount;
update orders set status = 'PROCESSED' where id = rec.id;
end loop;
commit;
DBMS_OUTPUT.PUT_LINE('Processed ' || v_count || ' orders, total: ' || v_total);
EXCEPTION
when OTHERS then
rollback;
raise;
end;
```
**Mendix MDL:**
```mdl
create microflow MyModule.ACT_ProcessPendingOrders ()
returns string as $Result
begin
declare $count integer = 0;
declare $Total decimal = 0;
declare $Result string = '';
-- Retrieve pending orders (replaces CURSOR) — retrieve creates $OrderList,
-- never declare a list variable first (CE0053/CE0038, MDL040)
retrieve $OrderList from MyModule.Order
where status = 'PENDING';
-- Process each order (replaces FOR LOOP)
loop $Order in $OrderList
begin
set $count = $count + 1;
set $Total = $Total + $Order/Amount;
change $Order (status = 'PROCESSED');
commit $Order on error {
log error 'Failed to process order: ' + $Order/OrderNumber;
};
end loop;
log info 'Processed ' + toString($count) + ' orders, total: ' + toString($Total);
set $Result = 'Processed ' + toString($count) + ' orders';
return $Result;
end;
/
```
## UI Component Mapping
### Oracle Forms Items to Mendix Widgets
| Oracle Forms Item | Mendix Widget | MDL Syntax |
|-------------------|---------------|------------|
| Text Item | Text Input | `INPUT 'name' attribute attr` |
| Display Item | Text | `text 'content'` |
| Check Box | Check Box | `checkbox 'name' attribute attr` |
| Radio Group | Radio Buttons | `RADIO 'name' attribute attr` |
| List Item (LOV) | Drop-down | `dropdown 'name' attribute attr` |
| Push Button | Button | `button 'name' on CLICK ...` |
| Tab Canvas | Tab Container | `TAB_CONTAINER (TAB 'name' (...))` |
### Oracle Forms Blocks to Mendix DataViews
**Oracle Forms Block → Mendix DataView:**
```mdl
-- Single-record block
dataview source $Customer (
INPUT 'Code' attribute CustomerCode,
INPUT 'Name' attribute CustomerName
)
-- Multi-record block (tabular)
datagrid source $OrderList (
column 'OrderNumber' attribute OrderNumber,
column 'OrderDate' attribute OrderDate,
column 'Amount' attribute TotalAmount
)
```
### Master-Detail Pattern
**Oracle Forms Master-Detail → Mendix:**
```mdl
-- check-skip: shorthand pseudo-syntax sketch of the migrated page, not runnable
-- MDL. See create-page / overview-pages for real page syntax.
create page MyModule.CustomerOrders
layout Atlas_Default
title 'Customer Orders'
parameter $Customer: MyModule.Customer
widgets (
-- Master block
dataview source $Customer (
INPUT 'Code' attribute CustomerCode readonly,
INPUT 'Name' attribute CustomerName readonly
),
-- Detail block (orders for this customer)
datagrid 'OrderGrid' source database MyModule.Order
where '[MyModule.Order_Customer = $Customer]' (
column 'OrderNumber' attribute OrderNumber,
column 'OrderDate' attribute OrderDate,
column 'Amount' attribute TotalAmount
)
);
/
```
## Triggers to Microflows
### Common Trigger Mappings
| Oracle Forms Trigger | Mendix Implementation |
|---------------------|----------------------|
| `when-NEW-FORM-INSTANCE` | Page load microflow (data source) |
| `when-NEW-RECORD-INSTANCE` | OnChange microflow on data source |
| `when-VALIDATE-item` | OnChange microflow or validation in save |
| `when-VALIDATE-RECORD` | Validation microflow before save |
| `post-query` | Microflow data source with transformation |
| `PRE-insert` / `PRE-update` | Before commit event handler |
| `post-insert` / `post-update` | After commit event handler |
| `key-commit` | Save button action microflow |
| `on-error` | `on error { ... }` blocks |
## Migration Checklist
Before starting migration:
- [ ] Export Oracle Forms XML (.xml) or use Forms2XML utility
- [ ] Document all triggers and their purposes
- [ ] Map database tables to Mendix entities
- [ ] Identify LOVs and map to enumerations
- [ ] Check for structural keyword conflicts (Create, Delete, Begin, End, Return)
During migration:
- [ ] Create entities first (Phase 1)
- [ ] Create microflows second (Phase 2)
- [ ] Create pages last (Phase 3) - they can reference microflows
- [ ] Test validation patterns thoroughly
- [ ] Use `validation feedback $entity/attribute message 'message'` for all validations
After migration:
- [ ] Run `mxcli check script.mdl -p app.mpr --references`
- [ ] Open in Mendix Studio Pro to verify
- [ ] Test all validation scenarios
- [ ] Verify master-detail relationships work correctly
## Common Migration Errors
| Error | Cause | Fix |
|-------|-------|-----|
| "Parse error: mismatched input 'Create'" | Structural keyword as attribute | Use `"create"` (quoted) or rename |
| "microflow not found" | Referenced before created | Move microflow definition before page |
| "page not found" | Referenced before created | Move page definition earlier |
| "VALIDATION FEEDBACK requires attribute" | Missing attribute path | Use `validation feedback $entity/attribute message 'msg'` |
| CE0117 "Error in expression" | Missing module prefix | Use fully qualified names |
## Tips for Success
1. **Plan attribute names carefully**: Most words work unquoted; only structural keywords (`create`, `delete`, `begin`, `end`, `return`) need quoting
2. **Organize scripts by phase**: Entities → Microflows → Pages
3. **Test incrementally**: Migrate one form at a time
4. **Keep validation close to logic**: Embed validation in save microflows
5. **Document mappings**: Track which Oracle Forms items map to which Mendix elements
6. **Use meaningful names**: `ACT_Customer_Save` not `SUB_SAVE`
7. **Leverage CRUD generation**: Use `/create-crud` skill for standard operations
## Related Skills
- [/write-microflows](../write-microflows/SKILL.md) - Detailed microflow syntax
- [/create-crud](./create-crud.md) - Generate CRUD operations
- [/overview-pages](../overview-pages/SKILL.md) - Page building patterns
- [/master-detail-pages](../master-detail-pages/SKILL.md) - Master-detail layouts