Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .claude/skills/mendix/write-microflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -542,15 +542,34 @@ $NewProduct = create Test.Product (
- Closing `)` followed by semicolon
- Syntax aligned with CALL MICROFLOW/CALL JAVA ACTION

**The Commit flag** (Studio Pro's "Commit" dropdown) is the optional `commit`
modifier after the member list:

```mdl
$Order = create Sales.Order (Number = $Nr); -- Commit: No (default)
$Order = create Sales.Order (Number = $Nr) commit; -- Commit: Yes
$Order = create Sales.Order (Number = $Nr) commit without events;-- Commit: YesWithoutEvents
```

Omit it for the default. This is a **modifier on the create**, not the separate
`commit $Var;` activity — see COMMIT Object below for that one.

### CHANGE Object

```mdl
change $Product (
Name = $NewName,
ModifiedDate = [%CurrentDateTime%]);

-- Commit the changed object as part of the change activity
change $Product (Name = $NewName) commit;
change $Product (Name = $NewName) commit without events;

-- Refresh the changed object in the client
change $Product (Name = $NewName) refresh;

-- Both: commit comes first
change $Product (Name = $NewName) commit refresh;
```

**Note**: Only specify attributes you want to change. Syntax aligned with CREATE.
Expand Down
6 changes: 3 additions & 3 deletions cmd/mxcli/syntax/features_microflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@ func init() {
Keywords: []string{
"create object", "change object", "commit", "rollback",
"delete", "save", "persist", "modify object",
"with events", "refresh",
"with events", "refresh", "commit flag", "without events",
},
Syntax: "$Obj = CREATE Module.Entity (Attr = value);\nCHANGE $Obj (Attr = value);\nCOMMIT $Obj;\nCOMMIT $Obj WITH EVENTS;\nCOMMIT $Obj REFRESH;\nCOMMIT $Obj WITH EVENTS REFRESH;\nDELETE $Obj;\nROLLBACK $Obj;",
Example: "$NewOrder = CREATE MyModule.Order (\n OrderNumber = 'ORD-001',\n Quantity = $Quantity,\n CreateDate = [%CurrentDateTime%]\n);\n\nCHANGE $NewOrder (MyModule.Order_Customer = $Customer);\nCOMMIT $NewOrder WITH EVENTS;\nDELETE $OldOrder;\nROLLBACK $DraftOrder;",
Syntax: "$Obj = CREATE Module.Entity (Attr = value) [COMMIT [WITHOUT EVENTS]];\nCHANGE $Obj (Attr = value) [COMMIT [WITHOUT EVENTS]] [REFRESH];\nCOMMIT $Obj;\nCOMMIT $Obj WITH EVENTS;\nCOMMIT $Obj REFRESH;\nCOMMIT $Obj WITH EVENTS REFRESH;\nDELETE $Obj;\nROLLBACK $Obj;\n\n-- The COMMIT modifier on CREATE/CHANGE is the activity's Commit setting\n-- (omitted = No). The standalone COMMIT $Obj is a separate activity.",
Example: "$NewOrder = CREATE MyModule.Order (\n OrderNumber = 'ORD-001',\n Quantity = $Quantity,\n CreateDate = [%CurrentDateTime%]\n) COMMIT;\n\nCHANGE $NewOrder (MyModule.Order_Customer = $Customer) COMMIT REFRESH;\nCHANGE $Draft (Status = 'Imported') COMMIT WITHOUT EVENTS;\nDELETE $OldOrder;\nROLLBACK $DraftOrder;",
SeeAlso: []string{"microflow.retrieve", "microflow.variables"},
})

Expand Down
4 changes: 2 additions & 2 deletions docs/01-project/MDL_QUICK_REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,8 +270,8 @@ it is for pages.
| Entity declaration | `declare $entity Module.Entity;` | No AS keyword, no = empty |
| List declaration | `declare $list list of Module.Entity = empty;` | |
| Assignment | `set $Var = expression;` | Variable must be declared first |
| Create object | `$Var = create Module.Entity (attr = value);` | |
| Change object | `change $entity (attr = value) [refresh];` | `refresh` updates the changed object in the client |
| Create object | `$Var = create Module.Entity (attr = value) [commit [without events]];` | `commit` = Commit Yes, `commit without events` = YesWithoutEvents; omitted = No (the default) |
| Change object | `change $entity (attr = value) [commit [without events]] [refresh];` | `commit` as above, before `refresh`; `refresh` updates the changed object in the client |
| Commit | `commit $entity [with events] [refresh];` | |
| Delete | `delete $entity;` | |
| Rollback | `rollback $entity [refresh];` | Reverts uncommitted changes |
Expand Down
75 changes: 75 additions & 0 deletions mdl-examples/doctype-tests/779-commit-flag.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
-- Issue #779: the Commit flag on create/change activities.
--
-- Before this change MDL could not express the flag at all: the builder hardcoded
-- Commit: No at both call sites, so every create/change authored through MDL was
-- written as non-committing regardless of intent, and DESCRIBE MICROFLOW rendered a
-- committing activity identically to a non-committing one. A describe → edit →
-- re-exec round-trip therefore silently cleared commit boundaries — the
-- object-orphaning failure mode the issue reports chasing in Studio Pro.
--
-- Three values, matching Mendix's Microflows$Commit enum:
--
-- (no modifier) -> No (the default, omitted from DESCRIBE)
-- commit -> Yes
-- commit without events -> YesWithoutEvents
--
-- Verify the round-trip:
--
-- mxcli exec 779-commit-flag.mdl -p app.mpr
-- mxcli -p app.mpr -c "describe microflow Issue779.CommitVariants"
--
-- The DESCRIBE output must carry the same modifiers as the source below, and be
-- re-executable as-is.

create module Issue779;
create module role Issue779.User;

@position(100, 100)
create persistent entity Issue779.Order (
Number: string(50),
Status: string(20)
);

create microflow Issue779.CommitVariants ()
returns boolean
begin
-- Default: no modifier, Commit = No. Unchanged from before, so existing
-- scripts and existing DESCRIBE output are unaffected.
$Draft = create Issue779.Order (Number = 'ORD-0001', Status = 'Draft');

-- Commit = Yes. Enabling the flag is a one-word diff.
$Placed = create Issue779.Order (Number = 'ORD-0002', Status = 'Placed') commit;

-- Commit = YesWithoutEvents: persists without firing before/after commit events.
$Imported = create Issue779.Order (Number = 'ORD-0003', Status = 'Imported') commit without events;

-- No member list, still takes the modifier.
$Blank = create Issue779.Order commit;

-- change carries the same modifier, before the existing `refresh`.
change $Draft (Status = 'Confirmed') commit;
change $Placed (Status = 'Shipped') commit without events;
change $Imported (Status = 'Reconciled') commit refresh;

-- refresh alone still parses exactly as it did.
change $Blank (Status = 'Void') refresh;

return true;
end;
/

-- The modifier must not be confused with the standalone COMMIT activity. These are
-- two different things on adjacent lines; mandatory statement semicolons are what
-- make them decidable.
create microflow Issue779.ModifierVsActivity ()
returns boolean
begin
$A = create Issue779.Order (Number = 'ORD-0004');
commit $A;

$B = create Issue779.Order (Number = 'ORD-0005') commit;
commit $B with events;

return true;
end;
/
17 changes: 15 additions & 2 deletions mdl/ast/ast_microflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,21 +219,34 @@ type ChangeItem struct {
Value Expression // Value expression
}

// CreateObjectStmt represents: $Var = CREATE Entity (assignments) [ON ERROR ...]
// CommitFlag is the Commit setting on a create/change activity, matching Mendix's
// Microflows$Commit enum. The zero value is CommitNo, which is Mendix's default and
// is therefore omitted from DESCRIBE output.
type CommitFlag int

const (
CommitNo CommitFlag = iota // no COMMIT clause
CommitYes // COMMIT
CommitYesWithoutEvents // COMMIT WITHOUT EVENTS
)

// CreateObjectStmt represents: $Var = CREATE Entity (assignments) [COMMIT [WITHOUT EVENTS]] [ON ERROR ...]
type CreateObjectStmt struct {
Variable string // Variable name (without $ prefix)
EntityType QualifiedName // Entity type
Changes []ChangeItem // SET assignments
Commit CommitFlag // Commit setting (default CommitNo)
ErrorHandling *ErrorHandlingClause // Optional ON ERROR clause
Annotations *ActivityAnnotations // Optional @position, @caption, @color, @annotation
}

func (s *CreateObjectStmt) isMicroflowStatement() {}

// ChangeObjectStmt represents: CHANGE $Var (assignments)
// ChangeObjectStmt represents: CHANGE $Var (assignments) [COMMIT [WITHOUT EVENTS]] [REFRESH]
type ChangeObjectStmt struct {
Variable string // Variable name
Changes []ChangeItem // SET assignments
Commit CommitFlag // Commit setting (default CommitNo)
RefreshInClient bool // Whether to refresh in client
Annotations *ActivityAnnotations // Optional @position, @caption, @color, @annotation
}
Expand Down
4 changes: 2 additions & 2 deletions mdl/backend/mcp/microflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -789,9 +789,9 @@ func memberChangeType(t microflows.MemberChangeType) string {
// mfCommitType maps a CommitType onto the PED commit enum (Yes / YesWithoutEvents / No).
func mfCommitType(c microflows.CommitType) string {
switch c {
case microflows.CommitTypeYes, microflows.CommitTypeYesWithEvents:
case microflows.CommitTypeYes:
return "Yes"
case microflows.CommitTypeNoEvent:
case microflows.CommitTypeYesWithoutEvents:
return "YesWithoutEvents"
default:
return "No"
Expand Down
11 changes: 6 additions & 5 deletions mdl/backend/mcp/microflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,11 +438,12 @@ func TestMapObjectTree_Loop(t *testing.T) {

func TestMfCommitType(t *testing.T) {
cases := map[microflows.CommitType]string{
microflows.CommitTypeYes: "Yes",
microflows.CommitTypeYesWithEvents: "Yes",
microflows.CommitTypeNoEvent: "YesWithoutEvents",
microflows.CommitTypeNo: "No",
microflows.CommitType(""): "No",
microflows.CommitTypeYes: "Yes",
microflows.CommitTypeYesWithoutEvents: "YesWithoutEvents",
microflows.CommitTypeNo: "No",
// An unset flag is Mendix's default, No — the same as an unrecognised one.
microflows.CommitType(""): "No",
microflows.CommitType("Nonsense"): "No",
}
for in, want := range cases {
if got := mfCommitType(in); got != want {
Expand Down
18 changes: 16 additions & 2 deletions mdl/executor/cmd_microflows_builder_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,27 @@ func (fb *flowBuilder) addChangeVariableAction(s *ast.MfSetStmt) model.ID {
return activity.ID
}

// commitTypeOf maps the AST's Commit modifier onto the stored Mendix enum. Both
// create and change previously hardcoded CommitTypeNo, so any project authored or
// round-tripped through MDL had its commit flags silently cleared (#779).
func commitTypeOf(f ast.CommitFlag) microflows.CommitType {
switch f {
case ast.CommitYes:
return microflows.CommitTypeYes
case ast.CommitYesWithoutEvents:
return microflows.CommitTypeYesWithoutEvents
default:
return microflows.CommitTypeNo
}
}

// addCreateObjectAction creates a CREATE OBJECT statement.
func (fb *flowBuilder) addCreateObjectAction(s *ast.CreateObjectStmt) model.ID {
action := &microflows.CreateObjectAction{
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
ErrorHandlingType: fb.ehType(s.ErrorHandling),
OutputVariable: s.Variable,
Commit: microflows.CommitTypeNo,
Commit: commitTypeOf(s.Commit),
}
// Set entity reference as qualified name (BY_NAME_REFERENCE)
entityQN := ""
Expand Down Expand Up @@ -238,7 +252,7 @@ func (fb *flowBuilder) addChangeObjectAction(s *ast.ChangeObjectStmt) model.ID {
BaseElement: model.BaseElement{ID: model.ID(types.GenerateID())},
ErrorHandlingType: fb.ehType(nil),
ChangeVariable: s.Variable,
Commit: microflows.CommitTypeNo,
Commit: commitTypeOf(s.Commit),
RefreshInClient: s.RefreshInClient || len(s.Changes) == 0,
}

Expand Down
30 changes: 24 additions & 6 deletions mdl/executor/cmd_microflows_format_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,24 @@ import (

// formatActivity formats a single microflow activity as an MDL statement.

// commitModifier renders the Commit flag of a create/change activity as the MDL
// modifier, or "" for Mendix's default (No) so the common case stays unwritten.
// The leading space is included, since the caller appends it directly after the
// member list.
//
// Before #779 this was never emitted: DESCRIBE could not distinguish a committing
// create from a non-committing one, and re-executing its output cleared the flag.
func commitModifier(c microflows.CommitType) string {
switch c {
case microflows.CommitTypeYes:
return " commit"
case microflows.CommitTypeYesWithoutEvents:
return " commit without events"
default:
return ""
}
}

// escapeExpressionValue escapes raw control characters inside string literals
// of a Mendix expression value so it can be safely embedded in MDL output.
// The lexer's STRING_LITERAL rule forbids raw \r and \n inside single-quoted
Expand Down Expand Up @@ -256,9 +274,9 @@ func formatAction(
}
members = append(members, fmt.Sprintf("%s = %s", memberName, escapeExpressionValue(m.Value)))
}
return fmt.Sprintf("$%s = create %s (%s);", outputVar, entityName, strings.Join(members, ", "))
return fmt.Sprintf("$%s = create %s (%s)%s;", outputVar, entityName, strings.Join(members, ", "), commitModifier(a.Commit))
}
return fmt.Sprintf("$%s = create %s;", outputVar, entityName)
return fmt.Sprintf("$%s = create %s%s;", outputVar, entityName, commitModifier(a.Commit))

case *microflows.ChangeObjectAction:
varName := a.ChangeVariable
Expand Down Expand Up @@ -287,14 +305,14 @@ func formatAction(
members = append(members, fmt.Sprintf("%s = %s", memberName, escapeExpressionValue(m.Value)))
}
if a.RefreshInClient {
return fmt.Sprintf("change $%s (%s) refresh;", varName, strings.Join(members, ", "))
return fmt.Sprintf("change $%s (%s)%s refresh;", varName, strings.Join(members, ", "), commitModifier(a.Commit))
}
return fmt.Sprintf("change $%s (%s);", varName, strings.Join(members, ", "))
return fmt.Sprintf("change $%s (%s)%s;", varName, strings.Join(members, ", "), commitModifier(a.Commit))
}
if a.RefreshInClient {
return fmt.Sprintf("change $%s refresh;", varName)
return fmt.Sprintf("change $%s%s refresh;", varName, commitModifier(a.Commit))
}
return fmt.Sprintf("change $%s;", varName)
return fmt.Sprintf("change $%s%s;", varName, commitModifier(a.Commit))

case *microflows.CommitObjectsAction:
varName := a.CommitVariable
Expand Down
Loading
Loading