Skip to content

alter microflow … replace loop: a find … by Member = value inside the new loop is stored as a find by an invalid expression (CE0117), because the loop body's builder does not inherit qualifiedMembersOnly #1673

Open

Description

@ynnckw

Summary

When alter microflow … { replace loop … with begin … end; } writes a loop whose body holds
$X = find $List by Member = value;, and the fragment does not know the entity of $List, the find is stored as
Microflows$FindByExpression with the bare text Member = value. check --references and exec report nothing, and
Studio Pro's mx check then fails with CE0117. In this case $List comes from a retrieve over an association that
stands before the loop.

The same find written by create microflow is stored as Microflows$Find on the attribute. The same find inserted
outside a loop with alter … insert after is refused with a clear message, which is the guard added by
ako#891. Inside a loop of the fragment, that guard does not apply.

Steps to reproduce

Setup:

mdl 1;
create module MyModule;
create persistent entity MyModule.Order (Code: String(50));
create persistent entity MyModule.OrderLine (Code: String(50), Status: String(20));
create association MyModule.OrderLine_Order from MyModule.OrderLine to MyModule.Order;
create non-persistent entity MyModule.Lookup (Code: String(50));
create microflow MyModule.SUB_MatchLines ($Order: MyModule.Order, $Lookups: List of MyModule.Lookup) returns Integer as $Found
begin
  declare $Found Integer = 0;
  retrieve $Lines from $Order/MyModule.OrderLine_Order;
  loop $Lookup in $Lookups
  begin
    $Line = find $Lines by Code = $Lookup/Code;
    if $Line != empty then
      set $Found = $Found + 1;
    end if;
  end loop;
  return $Found;
end;

The alter replaces the loop with its own text:

mdl 1;
alter microflow MyModule.SUB_MatchLines {
  replace loop $Lookup in $Lookups with begin
    loop $Lookup in $Lookups
    begin
      $Line = find $Lines by Code = $Lookup/Code;
      if $Line != empty then
        set $Found = $Found + 1;
      end if;
    end loop;
  end;
};

Then:

mxcli -p app.mpr exec setup.mdl
mxcli -p app.mpr bson dump --type microflow --object MyModule.SUB_MatchLines
mxcli -p app.mpr check alter.mdl --references
mxcli -p app.mpr exec alter.mdl
mxcli -p app.mpr bson dump --type microflow --object MyModule.SUB_MatchLines
mx check -w app.mpr

Expected

Either the find is stored as before, Microflows$Find on MyModule.OrderLine.Code, or the alter is refused the way
an insert with the same find is refused, so that nothing invalid is written.

Actual

After the setup, the list operation of the find is:

"$Type": "Microflows$Find",
"Attribute": "MyModule.OrderLine.Code",
"Expression": "$Lookup/Code",

check alter.mdl --references prints "Check passed!", and exec alter.mdl prints "Altered microflow
MyModule.SUB_MatchLines" with exit code 0. Afterwards the list operation is:

"$Type": "Microflows$FindByExpression",
"Expression": "Code = $Lookup/Code",

and describe microflow prints $Line = find $Lines where Code = $Lookup/Code;. mx check -w reports one error:

[error] [CE0117] "Error(s) in expression." at List operation activity 'Find by expression'

The same app after the setup alone reports no error, and the warnings are the same, so the error comes from the
alter.

Controls (same app and module, measured in the same session)

  • $Lines as a parameter ($Lines: List of MyModule.OrderLine), same loop, same alter: stored as
    Microflows$Find on MyModule.OrderLine.Code.

  • $Lines from a database retrieve (retrieve $Lines from MyModule.OrderLine;), same loop, same alter: stored as
    Microflows$Find on MyModule.OrderLine.Code.

  • The same find after the association retrieve, inserted on the main line instead of inside a loop:

    mdl 1;
    create microflow MyModule.SUB_FindOnMainLine ($Order: MyModule.Order, $Code: String) returns Boolean as $IsFound
    begin
      retrieve $Lines from $Order/MyModule.OrderLine_Order;
      declare $IsFound Boolean = false;
      return $IsFound;
    end;
    alter microflow MyModule.SUB_FindOnMainLine {
      insert after $Lines begin
        $Line = find $Lines by Code = $Code;
      end;
    };

    is refused, exit code 1, and nothing is written:

    Created microflow: MyModule.SUB_FindOnMainLine
    Error: alter microflow MyModule.SUB_FindOnMainLine: insert after $Lines: the fragment has errors:
      - cannot qualify member "Code": the entity its variable holds is not known here, and a bare attribute name would make the project unloadable ("not a valid AttributeIdentifier"); name it in full as Module.Entity.Code
    Nothing was written: statement 1 was rolled back, and the project is as it was before exec
    

Workaround

Name the member in full inside the fragment, $Line = find $Lines by MyModule.OrderLine.Code = $Lookup/Code;. The
alter then stores Microflows$Find on MyModule.OrderLine.Code, describe prints the short form
find $Lines by Code = $Lookup/Code again, and mx check reports no error.

Cause (read from the v0.28.0 source, not verified with a patched build)

ako#891 added qualifiedMembersOnly to flowBuilder (mdl/executor/cmd_microflows_builder.go), set for a
fragment spliced into a stored flow, so that a member the builder cannot qualify is refused instead of written bare.
addLoopStatement in mdl/executor/cmd_microflows_builder_control.go builds the loop body with a new builder that
shares varTypes, declaredVars, measurer, backend and others, but not that flag:

loopBuilder := &flowBuilder{
    textLang:     fb.textLang,
    ...
    varTypes:     fb.varTypes,     // Share variable scope
    declaredVars: fb.declaredVars, // Share declared vars (fixes nil map panic)
    ...
}

So inside the loop the find falls back to the bare member, which is the find by an invalid expression that #891
describes. The builder of an error handler's body in addErrorHandlerFlow (cmd_microflows_builder_flows.go) does
not copy the flag either; a find inside a handler in a fragment was not measured.

A second, smaller point: the fragment does not know the entity of a list retrieved over an association, while it
knows it for a list parameter and for a database retrieve. That is what makes the member unresolvable here; the
insert above shows it as a refusal.

Suggested fix

Copy qualifiedMembersOnly into every nested builder (loop and while bodies, error handlers, and any other builder a
fragment creates), so that the guard of #891 holds at every depth. Separately, type the output of a retrieve over an
association in the variables a fragment starts from, so that this find can be written as Microflows$Find without
the full name.

Environment

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions