Project

General

Profile

Bug #9078

emit FindQuery as (reused) variables

Added by Constantin Asofiei almost 2 years ago. Updated 11 months ago.

Status:
Review
Priority:
Normal
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
production:
No
env_name:
topics:

Related issues

Related to Base Language - Bug #9032: Reduce the number of exceptions that are being thrown WIP
Related to Database - Feature #10436: Detect Sequential Data Access and Prefetch Records from the Database WIP

History

#1 Updated by Constantin Asofiei almost 2 years ago

  • Related to Bug #9032: Reduce the number of exceptions that are being thrown added

#2 Updated by Constantin Asofiei almost 2 years ago

A FIND statement can be executed 10k's or more just for simple API calls. This requires, for each execution:
  • a new FindQuery instance to be created
  • state about the query to be re-built
The point of this task is to emit the FIND statements as a variable scoped to the top-level block. This top-level block will be:
  • the Java method for a function, internal procedure or class method/constructor (static or not)
  • the Java class for an external procedure as an instance field (emitting this as variable for the 'execute' Java method just means pushing this as an argument at the 'body' lambda call, and I would avoid this).

The FindQuery will re-use its internal state as long as the buffer(s) are the same - if these are not the same, it will get re-initialized. So this task requires both conversion changes and runtime changes, to be able to re-use the FindQuery instance.

I suggest assigning this to Danut after #9032 is finished (the part about 'silent' mode for FIND statements).

#3 Updated by Constantin Asofiei almost 2 years ago

There is another point here. When a Java method gets called 10k's of times, then the local-var FIND will be created each time. This will not solve the main issue: the solution is to move all FIND instances at the external program (Java class fields, static or not, depending on where is used), and ensure compatibility with recursive calls. This would mean that the FindQuery instance needs to track 'nested mode' and if the call is recursive, then create another instance and not reuse the same FindQuery instance (at the class field); this needs to be managed by runtime.

#6 Updated by Alexandru Lungu almost 2 years ago

  • Assignee set to Dănuț Filimon

#7 Updated by Dănuț Filimon over 1 year ago

  • Status changed from New to WIP

#8 Updated by Dănuț Filimon over 1 year ago

My plan is to analyze a customer application (using a test scenario) for:
  • The number of total FindQuery instances created;
  • How many FindQuery instance are used in a java method for a function/internal procedure/class method/constructor;
  • How many FindQuery instances are used in external procedures ('execute' method);
  • How many FindQuery instances were the buffers change/remain the same;
  • How many FindQuery instances end up in recursive calls (this reminded me of #6709 where I added a nested find report, not sure if it's relevant as it only looks for nested FINDs in FOR EACH queries);
Questions:
  • A part of the analysis can be done on the converted code (how many FindQuery instances can be found in the application and where are those defined), my idea is to only focus on a customer scenario and monitor the runtime. Is this alright?
  • In what scenario can the buffers of a FindQuery change (if we imagine that we already have #9078 implemented)?

#9 Updated by Constantin Asofiei over 1 year ago

Dănuț Filimon wrote:

  • A part of the analysis can be done on the converted code (how many FindQuery instances can be found in the application and where are those defined), my idea is to only focus on a customer scenario and monitor the runtime. Is this alright?

You can have a single FIND statement in a block, and executed a million times - so yes, doing some profiling to see how many FIND queries are created is better.

  • In what scenario can the buffers of a FindQuery change (if we imagine that we already have #9078 implemented)?

See the AdaptiveFind issue: a buffer can switch its bound buffer at any time. But otherwise, the proxy reference remains the same.

#10 Updated by Dănuț Filimon over 1 year ago

After a quick analysis I found the following:
  • Instances created: 6900539
  • Instances created in a constructor call: 1022
  • Instances created by execute method calls: 385039
  • Instances created by other methods: 6514478 (excluding destructors - there were no calls for this type of methods)

The test was done on an extended performance scenario from a customer using 7156c/15502 (100 runs).

#11 Updated by Dănuț Filimon over 1 year ago

After a retest of the scenario mentioned in #9078, I managed to gather the following information:
  • Instances created: 6900552
  • Instances created in a constructor call: 1022
  • Instances created by execute method calls: 385039
  • Instances created by other methods: 6514491
Breaking down on which FindQuery is used the most, I made a top for each category (from constructor, execute method and other calls):
Top From constructor From execute From internal procedures/functions/class methods
1 1022 111403 883905
2 - 45248 416416
3 - 45248 399152
4 - 38481 331086
5 - 38481 324422
6 - 29997 296447
7 - 29997 161196
8 - 19594 160114
9 - 12221 137162
10 - 10504 131243
11 - 2626 113049
12 - 606 112125
13 - 404 93324
14 - 202 78085
15 - 23 75447
16 - 2 73855
17 - 1 71912
18 - 1 70947
19 - - 66660
20 - - 66660
21 - - 66660
22 - - 58582
23 - - 55146
24 - - 51624
25 - - 48987

The top is based on the class name, method name and line on which the FindQuery is initialized.

#12 Updated by Alexandru Lungu over 1 year ago

What does first place mean? Is the same FIND Query 1022 times in a constructor, 111403 in an execute and 883905 in an internal procedure. How can a FindQuery from a specific class/method/line be in the constructor, execute and internal proc at the same time? I don't understand.

#13 Updated by Dănuț Filimon over 1 year ago

Alexandru Lungu wrote:

What does first place mean? Is the same FIND Query 1022 times in a constructor, 111403 in an execute and 883905 in an internal procedure. How can a FindQuery from a specific class/method/line be in the constructor, execute and internal proc at the same time? I don't understand.

I can't post the class names/methods because it is related to a customer. The tops are 'separated' from each other, when looking at the constructor calls there is a single constructor which initialized FindQuery instances in the same place 1022 times. FindQuery instances are either created from a constructor, execute method or function/internal procedure/class method and are distinct so there is no way the same FindQuery is initialized in both the constructor and execute method.

#14 Updated by Alexandru Lungu over 1 year ago

I think I get it ... there is no link between the columns. So on line 1, there are actual 3 different find queries.

TBH: 883905 is quite a large number :) In theory, extracting this as a class member should reduce the total count number. We can reduce to 1, if the query "blueprint" would be static - I think this is the goal anyway.

#15 Updated by Dănuț Filimon over 1 year ago

I am currently working on adding a "qryname" annotation to each find query (not just for the AdaptiveFind) but I am facing a problem where I am adding the annotation in Code Conversion Annotations step but it is no longer available in the Core Code Conversion step.

#16 Updated by Dănuț Filimon over 1 year ago

Dănuț Filimon wrote:

I am currently working on adding a "qryname" annotation to each find query (not just for the AdaptiveFind) but I am facing a problem where I am adding the annotation in Code Conversion Annotations step but it is no longer available in the Core Code Conversion step.

The annotation is "removed" in the same Code Conversion Annotations step. I quoted removed because there is no removeAnnotation() or cleanAnnotations() method being called at all.

#17 Updated by Dănuț Filimon over 1 year ago

The issue was related to adding an annotation to this and not copy, after this I managed to make a few more changes to get the following result:
  • Before
       @LegacySignature(type = Type.MAIN, name = "start.p")
       public void execute()
       {
          externalProcedure(Start.this, new Block((Body) () -> 
          {
             RecordBuffer.openScope(tt1);
             tt1.create();
             tt1.setF1(new integer(1));
             tt1.release();
             new FindQuery(tt1, (String) null, null, "tt1.recid asc").first();
          }));
       }
    
  • After
       @LegacySignature(type = Type.MAIN, name = "start.p")
       public void execute()
       {
          FindQuery query0 = new FindQuery();
    
          externalProcedure(Start.this, new Block((Body) () -> 
          {
             RecordBuffer.openScope(tt1);
             tt1.create();
             tt1.setF1(new integer(1));
             tt1.release();
             query0.initialize(tt1, ((String) null), null, "tt1.recid asc").first();
          }));
       }
    

I know that the query should be defined as an instance field because of the execute method (this is WIP).

Committed my changes to 9078a/15517.

#18 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15518. Added a default constructor for FindQuery so that the converted code compiles.

#19 Updated by Alexandru Lungu over 1 year ago

I am not sure if the FindQuery should be generated as a class member anyway. Its scope can remain there. Otherwise, for a persistent procedure, we will "carry" that find query till the end of life of the procedure. It doesn't make sense as the query would be used only inside the external scope anyway. I think it can remain there even for excute cases, because internal procedures don't need that.

#20 Updated by Dănuț Filimon over 1 year ago

Alexandru Lungu wrote:

I am not sure if the FindQuery should be generated as a class member anyway. Its scope can remain there. Otherwise, for a persistent procedure, we will "carry" that find query till the end of life of the procedure. It doesn't make sense as the query would be used only inside the external scope anyway. I think it can remain there even for excute cases, because internal procedures don't need that.

Thanks! I was planning on leaving this at the end because the call

<action>execLib("define_and_construct", classname, qryname)</action>
is not entirely correct and I'll need to extract the method and investigate it. One example where it fails:
   @LegacySignature(type = Type.METHOD, name = "test19")
   @Test(order = 19)
   public void test19()
   {
      object<? extends com.goldencode.p2j.oo.lang.LegacyError> ex_top = TypeFactory.object(com.goldencode.p2j.oo.lang.LegacyError.class);

      internalProcedure(Test9032Fqnext1.class, this, "test19", new Block((Init) () -> 
      {
         catchError(com.goldencode.p2j.oo.lang.LegacyError.class, 
         ex -> 
         {
            ex_top.assign(ex);
            Assert.isTrue(new logical(false), new character("Should not get exception - test19"));
         });
      }, 
      (Body) () -> 
      {
         FindQuery query18 = new FindQuery();

         OnPhrase[] onPhrase0 = new OnPhrase[]
         {
            new OnPhrase(Condition.ERROR, Action.THROW, "blockLabel0")
         };

         doBlock(TransactionType.FULL, "blockLabel0", onPhrase0, new Block((Body) () -> 
         {
            {
               query18.initialize(tt46, ((String) null), null, "tt46.recid asc").next();
            }
         }));

         Assert.isFalse(tt46.available());
      }));
   }

#21 Updated by Dănuț Filimon over 1 year ago

Another known problem where the FindQuery is not properly converted:

   @LegacySignature(type = Type.METHOD, name = "test1")
   @Test(order = 1)
   public void test1()
   {
      object<? extends com.goldencode.p2j.oo.lang.LegacyError> ex_top = TypeFactory.object(com.goldencode.p2j.oo.lang.LegacyError.class);

      internalProcedure(Test9032Fqcurrent1.class, this, "test1", new Block((Init) () -> 
      {
         catchError(com.goldencode.p2j.oo.lang.LegacyError.class, 
         ex -> 
         {
            ex_top.assign(ex);
            Assert.isTrue(new logical(false), new character("Should not get exception - test1"));
         });
      }, 
      (Body) () -> 
      {
         new FindQuery().initialize(tt1, ((String) null), null, "tt1.recid asc").current();
         Assert.isFalse(tt1.available());
      }));
   }

#22 Updated by Dănuț Filimon over 1 year ago

I have trouble differentiating between the top block execute method and top block class method. Because of #9078-20 and how 9078a is implemented, it find the first available top level block but if there are multiple nested blocks it should search for the farthest block control point. This is a problem because the farthest block will always be the execute method of the class, converting to:

   @LegacySignature(type = Type.EXECUTE)
   public void __unit_Test9032Fqcurrent3_execute__()
   {
      RecordBuffer.openScope(tt11);
      onRoutineLevel(Condition.ERROR, Action.THROW);
      {
      }
      FindQuery query0 = new FindQuery();
      FindQuery query1 = new FindQuery();
      FindQuery query2 = new FindQuery();
      FindQuery query3 = new FindQuery();
      FindQuery query4 = new FindQuery();
      ...

#23 Updated by Dănuț Filimon over 1 year ago

I've managed to fix #9078-21, there was another rule whre the FindQuery constructor was created so I had to change it to generate a variable. Still working on #9078-20 and #9078-22 problem.

#24 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 0 to 20

#25 Updated by Dănuț Filimon over 1 year ago

I've managed to find a fix for #9078-22, it involved searching for a top level block that has a top level ancestor and this will return a null for the cases where there are none (execute method) and will try searching for the current top level block that corresponds to the class.

I also found an additional scenarios that I overlooked, CAN-FIND:

   @LegacySignature(type = Type.METHOD, name = "TestFunctionality")
   @Test(order = 1)
   public void testFunctionality()
   {
      internalProcedure(TestCanFind.class, this, "TestFunctionality", new Block((Body) () -> 
      {
         Assert.isFalse(new FindQuery(ttitem, (String) null, null, "ttitem.recid asc", LockType.NONE).hasOne());     
      }, 
      (Fini) () -> 
      {
         ttitem.deleteAll();
      }));
   }

#26 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15519. Support FindQuery constructor conversion as a variable scoped to the top level block.

TODO: Improve rules

Next step: CAN-FIND conversion.

#27 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15520. Modified CAN-FIND conversion to match the FIND statement conversion.

I discovered a new conversion issue while testing hotel_gui so I am currently investigating it.

#28 Updated by Dănuț Filimon over 1 year ago

Dănuț Filimon wrote:

I discovered a new conversion issue while testing hotel_gui so I am currently investigating it.

The issue is related to choosing the right query name in some cases. For example using a DO PRESELECT where there is no FindQuery instance created (it is a PreselectQuery). I had my concerns regarding this, I'll have to make sure that the query names are generated and used correctly.

#29 Updated by Dănuț Filimon over 1 year ago

Dănuț Filimon wrote:

Dănuț Filimon wrote:

I discovered a new conversion issue while testing hotel_gui so I am currently investigating it.

The issue is related to choosing the right query name in some cases. For example using a DO PRESELECT where there is no FindQuery instance created (it is a PreselectQuery). I had my concerns regarding this, I'll have to make sure that the query names are generated and used correctly.

Fixed this issue in 9078a/15521. The solution was to assign the query name a bit later, enough for the block query annotation to be created. This annotation is checked before a new one is created for the FIND statement.

#30 Updated by Dănuț Filimon over 1 year ago

9078a/15521 commit reverts most of the effort done for 9078a. I will have to think of another solution.

#31 Updated by Dănuț Filimon over 1 year ago

Dănuț Filimon wrote:

9078a/15521 commit reverts most of the effort done for 9078a. I will have to think of another solution.

I reverted the changes from 9078a/15521 in 9078a/15522. Currently working on another solution.

#32 Updated by Dănuț Filimon over 1 year ago

I found a new fix for #9078-28, after converting hotel_gui and comparing the old and new conversions, I found the following scenarios:
  • AddRoomsDialog.execute():
       @LegacySignature(type = Type.MAIN, name = "add-rooms-dialog.w", parameters = 
       {
          @LegacyParameter(name = "needRefresh", type = "LOGICAL", mode = "OUTPUT")
       })
       public void execute(final logical _needRefresh)
       {
          externalProcedure(AddRoomsDialog.this, new Block((Init) () -> 
          {
             ...
          }, 
          (Body) () -> 
          {
             registerTrigger(new EventList("CHOOSE", gdialogFrame.widgetBtnOk()), AddRoomsDialog.this, () -> new Trigger()
             {
                ...
                FindQuery query0 = new FindQuery();
    
                public void body()
                {
                }
             }
          }
       }
    
  • GuestsFrame.execute():
             FindQuery query1 = new FindQuery();
    
             registerTrigger(new EventList("CHOOSE", fmainFrame.widgetInvoiceButton()), GuestsFrame.this, new Trigger((Body) () -> 
             {
                ...
    
                query1.initialize(xrptStay, "xrptStay.recid = ?", null, "xrptStay.stayId asc", new Object[]
                {
                   (P2JQuery.Parameter) () -> stay.recordID()
                }).first();
                ...
             }));
    

#33 Updated by Dănuț Filimon over 1 year ago

This one also:

   @LegacySignature(type = Type.MAIN, name = "start.p")
   public void execute()
   {
      externalProcedure(Start.this, new Block((Body) () -> 
      {
         RecordBuffer.openScope(tt1);

         PreselectQuery query2 = new PreselectQuery();

         doBlock("blockLabel0", new Block((Init) () -> 
         {
            query2.initialize(tt1, ((String) null), null, "tt1.recid asc");
         }, 
         (Body) () -> 
         {
            query2.first();
         }));

         AdaptiveQuery query3 = new AdaptiveQuery();
         FindQuery query1 = new FindQuery();

         forEach(query3, "loopLabel0", new Block((Init) () -> 
         {
            query3.initialize(tt1, ((String) null), null, "tt1.recid asc");
         }, 
         (Body) () -> 
         {
            query1.initialize(tt1, ((String) null), null, "tt1.recid asc").first();
         }));
      }));
   }

define temp-table tt1 field f1 as integer.

do preselect each tt1.
   find first tt1.
end.

for each tt1.
   find first tt1.
end.

#34 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15523. Added the fix mentioned in #9078-32.

#35 Updated by Dănuț Filimon over 1 year ago

Fixed #9078-33 in 9078a/15524.

#36 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15525.
  • FIND statements are annotated using "fqryname" annotation instead of "qryname". Because those statements are processed much earlier, there are unrelated queries that receive a different query name so comparing conversion is much harder.
  • For AdaptiveFind, annotating "qryname" takes priority over "fqryname" so it this kind of situations it will be removed.
Constantin, at this point the conversion changes for #9032-2 are implemented, but during yesterday's standup you mentioned there are some cases related to INNER_BLOCK where we should keep the old conversion. My questions are:
  1. Can you clarify the cases where we should keep the old conversion? You mentioned that #9032-33 should not emit the FindQuery outside of the FOR EACH block and that the GuestsFrame.execute() from #9032-32 should not emit the FindQuery outside of the registerTrigger while the AddRoomsDialog.execute() is fine. I want to make sure I have a clear understanding of all scenarios and none show up later (at this moment all FindQuery constructors are emitted at the beginning of each method, while the #9032-32 remained as it is).
  2. What stops us from doing a cleanup of the FindQuery and reusing it in cases like #9078-33?

#37 Updated by Dănuț Filimon over 1 year ago

Rebased 9078a with trunk/15526, the branch is now at revision 15535.

#38 Updated by Constantin Asofiei over 1 year ago

Dănuț Filimon wrote:

Constantin, at this point the conversion changes for #9032-2 are implemented, but during yesterday's standup you mentioned there are some cases related to INNER_BLOCK where we should keep the old conversion. My questions are:
  1. Can you clarify the cases where we should keep the old conversion? You mentioned that #9032-33 should not emit the FindQuery outside of the FOR EACH block and that the GuestsFrame.execute() from #9032-32 should not emit the FindQuery outside of the registerTrigger while the AddRoomsDialog.execute() is fine. I want to make sure I have a clear understanding of all scenarios and none show up later (at this moment all FindQuery constructors are emitted at the beginning of each method, while the #9032-32 remained as it is).

No, the point is: if you have a FIND for which its nearest block parent is an INNER-BLOCK (which is also a loop), then extract the FindQuery instance at the nearest top-level block (either as a var def at the Java method or inside the top-level block, doesn't matter as long is outside of all parent loop blocks).

  1. What stops us from doing a cleanup of the FindQuery and reusing it in cases like #9078-33?

When a FIND is used directly in the top-level block (so there is no 'looping block' as parent), that FIND instance can not ever be re-used - is a 'one time and done'. If you extract it as a Java method var, it doesn't help or hurt. The point was to keep the code 'clean' to be easier to check for conversion changes. Also, just a reminder, we can't promote a FindQuery scoped to an internal procedure/function as a class field because of recursivity.

#39 Updated by Dănuț Filimon over 1 year ago

Constantin Asofiei wrote:

Dănuț Filimon wrote:

Constantin, at this point the conversion changes for #9032-2 are implemented, but during yesterday's standup you mentioned there are some cases related to INNER_BLOCK where we should keep the old conversion. My questions are:
  1. Can you clarify the cases where we should keep the old conversion? You mentioned that #9032-33 should not emit the FindQuery outside of the FOR EACH block and that the GuestsFrame.execute() from #9032-32 should not emit the FindQuery outside of the registerTrigger while the AddRoomsDialog.execute() is fine. I want to make sure I have a clear understanding of all scenarios and none show up later (at this moment all FindQuery constructors are emitted at the beginning of each method, while the #9032-32 remained as it is).

No, the point is: if you have a FIND for which its nearest block parent is an INNER-BLOCK (which is also a loop), then extract the FindQuery instance at the nearest top-level block (either as a var def at the Java method or inside the top-level block, doesn't matter as long is outside of all parent loop blocks).

  1. What stops us from doing a cleanup of the FindQuery and reusing it in cases like #9078-33?

When a FIND is used directly in the top-level block (so there is no 'looping block' as parent), that FIND instance can not ever be re-used - is a 'one time and done'. If you extract it as a Java method var, it doesn't help or hurt. The point was to keep the code 'clean' to be easier to check for conversion changes. Also, just a reminder, we can't promote a FindQuery scoped to an internal procedure/function as a class field because of recursivity.

Thanks for the clarification! My next step is to create more test cases and investigate the conversion in such cases.

#40 Updated by Constantin Asofiei over 1 year ago

Danut, the FindQuery var def should be similar to the OPEN QUERY var def - see convert/database_access.rules around line 986. Maybe this helps.

#41 Updated by Dănuț Filimon over 1 year ago

Constantin Asofiei wrote:

Danut, the FindQuery var def should be similar to the OPEN QUERY var def - see convert/database_access.rules around line 986. Maybe this helps.

It does help, I think it is even simpler than what I've done until now. I'll have to try a few things right away, thanks!

#42 Updated by Dănuț Filimon over 1 year ago

I've managed to get a stable conversion with the new changes, but I found this case that needs to be investigated:

               ToClause toClause0 = new ToClause(i, firstVal, lastVal);

               repeatTo(TransactionType.FULL, "loopLabel1", toClause0, new Block((Body) () -> 
               {
                  new FindQuery(room, "room.roomNum = ?", null, "room.roomNum asc", new Object[]
                  {
                     i
                  }).silentFirst();

                  ...
               }));
The Find Query should be emitted as a variable in this case.

#43 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 20 to 40
Committed 9078a/15536.
  • Only emit a FindQuery as a variable for looping inner blocks.
  • Fixed #9032-42.
  • Cleaned up rule changes.

My test cases and hotel_gui convert properly.

#44 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15537. Fixed wrong order of emitted FindQuery variable.

#45 Updated by Dănuț Filimon over 1 year ago

  • Status changed from WIP to Review

Constantin, as discussed, please review the changes when you have time.

#46 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15538.

  • Extended field initialization in FindQuery to support the new conversion changes. Fields are initialized through a initializeFields() method (can't name it initialize() because of the ambiguity with a RandomAccessQuery method)

Next step: Investigate the condition to determine if the used buffers are the same (I suspect the same should be done for the DMO used by the query in a join via a foreign relation).

#47 Updated by Constantin Asofiei over 1 year ago

Review of 9078a/15537:
  • hasAncestorLoopingInnerBlock - this needs to look through all 'inner_block', not just the immediate one, until the nearest top-level block is reached
  • in database_access.rules, this code:
                                  <while>
                                     toplvlref != null                                   and
                                     !(toplvlref.isAnnotation("toplevel")                and
                                       #(boolean) toplvlref.getAnnotation("toplevel"))
                                     <!-- walk up the tree -->
                                     <action>toplvlref = toplvlref.parent</action>
                                  </while>
    

    I think can be replaced with getAncestor(null, "toplevel", true, prog.block).
    • is it ever possible for <rule>toplvlref != null and toplvlref.isAnnotation("instvarid") to be false? I.e. an external program which just a REPEAT: FIND... ?
    • you have this code:
                                 <action>
                                    qryid = tw.graftAt("peer_method_call", copy,
                                                       location, -1,
                                                       "javaname", qryname,
                                                       "methodtxt", "initialize").id
                                 </action>-->
      
      • there is a wrong? --> char?
      • what would be location here?
    • the code between lines 2003-2017 and 2032-2045 is duplicated. Why not calculate the 'top level ref' once and after that do the work?
    • this condition:
                           <rule on="false">qryname == null
                              <!-- no qryname means that there is no query to assign to (e.g.
                                   FindQuery) -->
                              <action>
                                 qryid = createPeerAst(java.constructor, classname, location)
                              </action>
      

      was moved outside of the rule from line 1950 to inside the rule on line 1994 (<rule on="false">classname.equals("FindQuery")), and previously condition was <rule on="false">qryname == null. I'm not sure this is the same as it worked previously.

#48 Updated by Dănuț Filimon over 1 year ago

  • Status changed from Review to WIP

Constantin Asofiei wrote:

Review of 9078a/15537:
  • is it ever possible for <rule>toplvlref != null and toplvlref.isAnnotation("instvarid") to be false? I.e. an external program which just a REPEAT: FIND... ?

I remember the following line failed as there was no "instvarid" annotation:

<action>olocation = toplvlref.getAnnotation("instvarid")</action>
But I've made several changes after that and with the ones I need to make for this review I will be able to check again.

  • you have this code:
    [...]
    • there is a wrong? --> char?

Oops, missed that one!

  • what would be location here?

The olocation is where the FindQuery will be emitted as a variable, while the location is where the initialize method will be attached to the variable name of the query (will add a comment in the next commit).

  • the code between lines 2003-2017 and 2032-2045 is duplicated. Why not calculate the 'top level ref' once and after that do the work?

Will do!

  • this condition:
    [...]
    was moved outside of the rule from line 1950 to inside the rule on line 1994 (<rule on="false">classname.equals("FindQuery")), and previously condition was <rule on="false">qryname null. I'm not sure this is the same as it worked previously.

From my understanding, there are no longer queries without a name. Each FindQuery will have a name, even if it is not emitted as a variable so the qryname null was not needed anymore as there was no case I could think of where a query does not have a name (maybe a CompoundQuery component?).

Thanks for the review!

#49 Updated by Alexandru Lungu over 1 year ago

From my understanding, there are no longer queries without a name. Each FindQuery will have a name, even if it is not emitted as a variable so the qryname null was not needed anymore as there was no case I could think of where a query does not have a name (maybe a CompoundQuery component?).

Just a quick hint: CAN-FIND statements were previously generated without a name, because they were inlined. For instance a = CAN-FIND(tt) would generate a FindQuery, but won't assign it to a variable because the condition doesn't allow the definition of queries ad-hoc. If you took care of CAN-FIND queries, I think I am ok with this.

#50 Updated by Dănuț Filimon over 1 year ago

Alexandru Lungu wrote:

From my understanding, there are no longer queries without a name. Each FindQuery will have a name, even if it is not emitted as a variable so the qryname null was not needed anymore as there was no case I could think of where a query does not have a name (maybe a CompoundQuery component?).

Just a quick hint: CAN-FIND statements were previously generated without a name, because they were inlined. For instance a = CAN-FIND(tt) would generate a FindQuery, but won't assign it to a variable because the condition doesn't allow the definition of queries ad-hoc. If you took care of CAN-FIND queries, I think I am ok with this.

I also assigned a query name to CAN-FIND queries.

#51 Updated by Dănuț Filimon over 1 year ago

Committed 9078a/15539. Addressed #9078-47.

I think something like

<action on="false">toplvlref = copy.getAncestor(-1, prog.block, "toplevel", true)</action>
will always return a block and I think that the following check to toplvlref != null is not required.

#52 Updated by Dănuț Filimon over 1 year ago

Fixed an action and removed the check in 9078a/15540.

#53 Updated by Dănuț Filimon over 1 year ago

Constantin Asofiei wrote:

See the AdaptiveFind issue: a buffer can switch its bound buffer at any time. But otherwise, the proxy reference remains the same.

Constantin, does this mean that I have to check the initialized buffer (RandomAccessQuery.buffer) so that it matches the buffer of the dmo? Also, how should I handle the inverse used to build the join? I suspect the DynamicJoin/DynamicLegacyKeyJoin will need to be rebuilt (in the case where the inverse buffer does not match but the dmo buffer does - if possible).

#54 Updated by Constantin Asofiei over 1 year ago

The only way to initialize the FIND is via FindQuery.initialize, right? All DMOs (the proxy) are there, save them as 'orig' and compare the bound buffer(s) the next time this gets initialized - if they differ, then re-initialize the instance. There may be some reset code to execute, to get the FindQuery in the 'initial' state, when initialization is not done.

#55 Updated by Dănuț Filimon over 1 year ago

Constantin Asofiei wrote:

The only way to initialize the FIND is via FindQuery.initialize, right?

Not quite, the fields are can be initialized through the constructor but also an initialize method (but I am making changes so that it reaches the same method - initializeFields). The method that actually does the initialization is initializeQuery.

All DMOs (the proxy) are there, save them as 'orig' and compare the bound buffer(s) the next time this gets initialized - if they differ, then re-initialize the instance. There may be some reset code to execute, to get the FindQuery in the 'initial' state, when initialization is not done.

I will be looking into it, thanks!

#56 Updated by Constantin Asofiei over 1 year ago

Dănuț Filimon wrote:

Constantin Asofiei wrote:

The only way to initialize the FIND is via FindQuery.initialize, right?

Not quite, the fields are can be initialized through the constructor but also an initialize method (but I am making changes so that it reaches the same method - initializeFields). The method that actually does the initialization is initializeQuery.

Well, for the 'promoted' FindQuery instances, the constructor will not receive anything, so you can mark these with some flag to distinguish the optimized and non-optimized instances.

#57 Updated by Dănuț Filimon over 1 year ago

Constantin Asofiei wrote:

Dănuț Filimon wrote:

Constantin Asofiei wrote:

The only way to initialize the FIND is via FindQuery.initialize, right?

Not quite, the fields are can be initialized through the constructor but also an initialize method (but I am making changes so that it reaches the same method - initializeFields). The method that actually does the initialization is initializeQuery.

Well, for the 'promoted' FindQuery instances, the constructor will not receive anything, so you can mark these with some flag to distinguish the optimized and non-optimized instances.

I did think of a nested flag (because of #9078-3), sounds like a plan!

#58 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 40 to 80

Committed 9078a/15541. Reinitialize the query when the bound buffers are different.

Left 20% for making adjustments and testing.

#59 Updated by Dănuț Filimon over 1 year ago

I managed to successfully convert a large customer application and after looking a bit of the sources I did not notice anything wrong (there are also a lot of changes so this makes it a bit harder).

#60 Updated by Dănuț Filimon over 1 year ago

One thing that I want to mention is that there were no error: code too large problems and an ongoing "issue". You can have a converted file and it only has a single fquery22 defined (it would normally start from 0), but the other names are claimed by existent FindQuery instances that did not benefit from this promotion.

#61 Updated by Dănuț Filimon over 1 year ago

Constantin, do you have any test case where the bound buffers can differ? I tried getting a snippet from a customer application that I am testing and changing it, but now the application is no longer working (not sure why) so I am planning to run a full conversion again.

#62 Updated by Constantin Asofiei over 1 year ago

Danut, the scenario would be like this, but OE doesn't allow unbind/re-bind of an already bound reference-only table see also https://community.progress.com/s/article/000057279:

def temp-table tt1 field f1 as int.
def temp-table tt2 field f1 as int.
def temp-table tt3 reference-only field f1 as int.

procedure proc0.
  def input param table for tt3 bind.
end.

procedure proc1.
  def input param table for tt3 bind.

end.

def var l as log.
l = true.
repeat:

   if l then run proc0(input table tt1 bind).
    else run proc1(input table tt2 bind).
   message temp-table tt3:name.
   l = not l.
   find first tt3 no-error.
   pause.
end.

#63 Updated by Dănuț Filimon over 1 year ago

I think I might have been chasing a totally different bug in a customer application (a bit weird), the following query:

         PresortQuery query5 = new PresortQuery();
         FieldReference byExpr1 = new FieldReference(ttable1, "field1");
         FieldReference byExpr2 = new FieldReference(ttable1, "field2");

         forEach(query5, "forLoop4", new Block((Init) () -> 
         {
            RecordBuffer.openScope(bufttableBuf2);
            query5.initialize(ttable1, "ttable1.field4 = true and ttable1.field5 = 1", null, "ttable1.field1 asc, ttable1.field3 asc");
            query5.enableBreakGroups();
            query5.setNonScrolling();
            query5.addSortCriterion(byExpr1);
            query5.addSortCriterion(byExpr2);
         }, 
         (Body) () -> 
         {
            ...
         }

There is an extra loop that starts with the following record in ttable1 buffer:

local/_temp/primary Ttable1_1 (ttable1@10:7#1) [tt195]
---------------------------------------------
tt195:3076
RecordState{NOUNDO CACHED }
references: 1
dirty: {}
unvalidated: {}
---------------------------------------------
   recid                :  3076
   __error-flag__       :  ?
   __origin-rowid__     :  ?
   __datasource-rowid__ :  ?
   __error-string__     :  ?
   __after-rowid__      :  ?
   __row-state__        :  ?
   ...
   field5               :  ?
   ...
   field4               :  true
   ...
field5 = ? while the query uses field5 = 1 so this does not look related to the FindQuery changes (neither the functions/procedures called from the problematic method).

I am using the trunk/15523 sources, while 9078a is based on trunk/15526. Right now I plan to use trunk/15526 and check if this error appears, then remove these 3 revisions (just for testing and this should be ok as those revisions only contain runtime changes), rebase the branch and run a new conversion if necessary.

#64 Updated by Dănuț Filimon over 1 year ago

#9078-63 can be ignored, after looking into the structure of the table there is an additional field similar to field5 and that is ffield5.

#65 Updated by Dănuț Filimon over 1 year ago

I've converted ETF with 9078a but all tests fail due to Cannot create temp table field for "BATCHACTION". I've also tried rebasing 9078a but there were recent changes done to the FindQuery to support multi-tenancy and it turned into game of cherry picking. My changes were not done with multi-tenancy in mind, so I'll probably have to create a new branch, then port and fix everything there.

#66 Updated by Dănuț Filimon over 1 year ago

I created 9078b from trunk/15621, after making a patch of 9078a on top of that branch I found conversion issues using my old tests. Currently working on fixing the tests, then I'll commit my changes.

#67 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 80 to 30

I will have to rework almost all the changes from database_access.rules because the constructor is no longer generating (even if the ast is created in the rules, it does not appear in the converted java file). I've already made progress with this.

My plan is this:
  1. reintroduce the removed changes in the current version of database_access.rules.
  2. setup dataset project to use multi-tenancy.
  3. convert etf, investigate the original FindQuery re-initialization problem
  4. investigate re-initialization when setMultiTenantAlternative() is used by the FindQuery
Goals:
  • step 1 & 2, application should convert and compile
  • step 3, find and fix the re-initialization problem
  • step 4, no regressions with multi-tenancy

#68 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 30 to 50

Committed 9078b/15622. This branch contains the changes from 9078a, with the database_access.rules adjusted to comply with recent FindQuery conversion changes.

I was successful in converting my test cases and hotel_gui.

#69 Updated by Dănuț Filimon over 1 year ago

The following FindQuery extracted from ETF (slightly modified table/field names):

               fquery26.initialize(tt1, "tt1.f1 = ? and upper(tt1.f2) = ?", null, "tt1.f1 asc, tt1.f2 asc", new Object[]
               {
                  if1,
                  (P2JQuery.Parameter) () -> toUpperCase(entry(itablef1, subscript(cget, if1)))
               }).silentFirst();
fails when executed in a DO type block because of the arguments.

Scenario:
  1. First iteration, FindQuery is initialized -> initialized flag set to true
  2. Find the record -> continue the execution flow
  3. Second iteration, query is already initialized (flag) so the arguments are not updated.
Solution:
  • Reinitialize the query when the arguments do not match.

#70 Updated by Dănuț Filimon over 1 year ago

Dănuț Filimon wrote:

Solution:
  • Reinitialize the query when the arguments do not match.

I found that even if the args are different and the query reinitialized, the RandomAccessQuery.currentArgs will still retain the old values! I only found this after looking into some tests that took considerably longer to execute (this can also result into infinite loops).

#71 Updated by Dănuț Filimon over 1 year ago

Example of the problem:

   @LegacySignature(type = Type.PROCEDURE, name = "test1")
   @com.goldencode.p2j.testengine.api.Test(order = 1)
   public void test1()
   {
      integer iicomtypid = TypeFactory.integer();
      FindQuery fquery0 = new FindQuery();

      internalProcedure(Test9078Arguments.this, "test1", new Block((Body) () -> 
      {
         iicomtypid.assign(10);

         repeat("loopLabel2", new Block((Body) () -> 
         {
            fquery0.initialize(tt90781, "tt90781.tableno = ?", null, "tt90781.recid asc", new Object[]
            {
               iicomtypid // contains the REFERENCE to the iicomtypid value, when it is updated the old arguments will be the new arguments, the query will not be reinitialized and the value stored in @currentArgs@ will still be 10
            }).silentFirst();
            Assert.isTrue(tt90781.available());

            if (_isEqual(tt90781.getTableno(), tt90781.getTableno1()))
            {
               leave("loopLabel2");
            }

            iicomtypid.assign(tt90781.getTableno1()); // updates the value after the first iteration
         }));

         Assert.legacyEquals(new integer(1), iicomtypid);
      }));
   }
doing a copy of the array is not possible because it can contain references to the values, updating conversion would be too much for this type of changes (I think?), maybe using duplicate() for BaseDataType values so that we do not use the reference (not sure about the overhead, but this is the safest change).

#72 Updated by Dănuț Filimon over 1 year ago

Committed 9078b/15623. Fix an infinite loop by making arguments part of the condition for reinitialization, some arguments are passed by reference and a copy of the argument must be done in such cases. after digging into it.

It seems that the performance drop I was seeing was actually due to an infinite loop, this issue is now fixed. One more test, then I can consider step 1 and 3 from #9078-67 complete.

#73 Updated by Dănuț Filimon over 1 year ago

Managed to setup the dataset project to use multi-tenancy and convert a few examples. The conversion is regressing with 9078b and I am currently investigating.

#74 Updated by Dănuț Filimon over 1 year ago

  • % Done changed from 50 to 70

Committed 9078b/15624. Fixed conversion compatibility with multi-tenancy, ETF conversion passed (matches previous conversion with 9078b/15623 in a non multi-tenant application).

Next step: investigate re-initialization when setMultiTenantAlternative() is used by the FindQuery (step 4 from #9078-67). I'll have to write additional tests that use persistent tables, similar to the ones I already have.

#75 Updated by Dănuț Filimon over 1 year ago

While working on a few tests, I found another conversion regression when using multi-tenancy where a find query that can be emitted at the top level block will reach this rule in database_access.rules:

                        <!-- When processing a [mt-cross-database] tainted [RECORD_PHRASE] node, just emit
                             [setMultiTenantAlternative], after [addExternalBuffers]. All the above methods
                             were emitted by the sibling [RECORD_PHRASE] which doesn't have the 
                             annotation -->
                        <rule on="false">true
                           <action>printfln("parent: %s", parent.dumpTree(true))</action>
                           <action>extbufid = #(long) parent.getAnnotation("extbufid")</action>
                           <action>
                              qryid = tw.graftAt("peer_method_call", copy, getAst(extbufid).parent.id, -1,
                                                 "javaname",  qryname,
                                                 "methodtxt", "setMultiTenantAlternative").id
                           </action>
                        </rule>
but the parent is:
     [java] FIND [KW_FIND]:21474836742 @24:9
     [java]       (fqryname=fquery0, support_level-byrule=gaps/gap_analysis_marking.xml:435, preselect_fetch=false, adaptive=false, support_level=16400, preselect_fetch-byrule=annotations/preselect_prep.rules:382, fqryname-byrule=annotations/database_general.rules:439, adaptive-byrule=annotations/adaptive_query.rules:321)
and does not have the "extbufid" (only used when handling FOR EACH statements). Any attempt to modify the location to match the existent one results in an additional dmo to appear as a parameter to the setMultiTenantAlternative or to be placed in a totally different part of the code.

#76 Updated by Dănuț Filimon over 1 year ago

Fixed the problem from #9078-75 in 9078b/15625. The rules rely on the "extbufid" annotation to emit the setMultiTenantAlternative method for all types of queries, but FindQueries can use the location directly.

#77 Updated by Dănuț Filimon about 1 year ago

Rebased 9078b to latest trunk/15969, the branch is now at revision 15973.

#78 Updated by Dănuț Filimon about 1 year ago

Artur ran a conversion for a customer application and found a conversion issue. I plan to work with Artur to identify the root cause and create a test case, the problematic file did contain more than 450 find queries so I'll have to make sure that the right methods are used.

#79 Updated by Dănuț Filimon about 1 year ago

I discussed with Artur and took time to take a look at all find queries from the problematic class. I took each one and there is a matching initialize() method being used. The compile error given is confusing, as it doesn't specify anything related to the find query:

compile:
    [javac] Compiling 1725 source files to /home/as/projects/<placeholder>/build/classes
    [javac] /home/as/projects/<placeholder>/<placeholder>.java:1: error: too many parameters
    [javac] /*v****************************************************************************
    [javac] ^
    [javac] Note: Some input files use or override a deprecated API.
    [javac] Note: Recompile with -Xlint:deprecation for details.
    [javac] Note: Some input files use unchecked or unsafe operations.
    [javac] Note: Recompile with -Xlint:unchecked for details.
    [javac] 1 error 
Note: I removed customer information.

The next step would be to setup the customer application myself and reproduce the issue.

#80 Updated by Dănuț Filimon about 1 year ago

I did not take into account that there might be a limitation to the number of local variables that can be defined in a method. In the execute() method, there are more than 450 FindQuery variables, but I am not yet sure if this is an actual limit for local variables.

#81 Updated by Alexandru Lungu about 1 year ago

Dănuț Filimon wrote:

I did not take into account that there might be a limitation to the number of local variables that can be defined in a method. In the execute() method, there are more than 450 FindQuery variables, but I am not yet sure if this is an actual limit for local variables.

Java has several limitations of that kind (number of methods a class can have, number of lines a method can have, etc.) maybe more. We also face this when converting code which ends up breaking these hard limits (which are not configurable). It is either:
  • refactor the customer code
  • find another conversion strategy

Maybe you can create an array and instead of emitting FindQuery findQuery123 = new FindQuery(..);, emit findQuery[123] = new FindQuery(..). Please think of other approaches and lets discuss here.

#82 Updated by Dănuț Filimon about 1 year ago

Alexandru Lungu wrote:

Dănuț Filimon wrote:

I did not take into account that there might be a limitation to the number of local variables that can be defined in a method. In the execute() method, there are more than 450 FindQuery variables, but I am not yet sure if this is an actual limit for local variables.

Java has several limitations of that kind (number of methods a class can have, number of lines a method can have, etc.) maybe more. We also face this when converting code which ends up breaking these hard limits (which are not configurable). It is either:
  • refactor the customer code
  • find another conversion strategy

Maybe you can create an array and instead of emitting FindQuery findQuery123 = new FindQuery(..);, emit findQuery[123] = new FindQuery(..). Please think of other approaches and lets discuss here.

I like this idea. I need to note that all FindQueries have a name that is replaced when handling an AdaptiveFind query, at the same time all FIND and CAN-FIND statements need a query name which is not used all the time (FindQuery instances are only emitted for inner blocks that are also loops). The conclusion findQuery[123] will not match the fquery123. There's also the option of using a Map where the key will be the query name and the value will be the instance, I believe this will also make the code easy to read.

I'll check both ideas and see which ones works better.

#83 Updated by Alexandru Lungu about 1 year ago

Danut, just keep in mind that a Map has a higher overhead than a plain array. That is why I am suggesting creating an array instead.

#84 Updated by Dănuț Filimon about 1 year ago

I found the named_array template in java_templates.tpl and managed to generate something like:

      FindQuery fquery0 = new FindQuery();
      FindQuery[] fqueryArray = new FindQuery[]
      {
      }
;

There are a few issues... The ; placement ant the fact that tried the following:
      <rule>fqueryCounter &gt; 0 and
            toplvlref != null
         <action>
            tw.graft("named_array",
                              toplvlref,
                              olocation,
                              "classname",
                              "FindQuery",
                              "javaname",
                              "fqueryArray",
                              "extent",
                              5)
         </action>
         <action>toplvlref = null</action>
         <action>olocation = null</action>
      </rule>
Ignore the fixed 5 for the extent, I wanted to understand why it is not creating the array as FindQuery[] fqueryArray = new FindQuery[5]. Maybe the template is not intended to be used like this and I should create a new one.

#85 Updated by Dănuț Filimon about 1 year ago

I've adjusted the template in the following way:

   <!-- a named array initializer -->
   <ast-root name="named_extent_array" terse="true" ast_class="com.goldencode.p2j.uast.JavaAst" >
      <ast id="0" type="ASSIGN" text="">
         <ast id="0" type="REFERENCE_DEF" text="${classname}">
            <annotation datatype="java.lang.String" key="name" value="${javaname}" />
            <annotation datatype="java.lang.Long" key="extent" value="${extent}" />
         </ast>
         <ast id="0" type="EXPRESSION" text="">
            <ast id="0" type="REFERENCE_DEF" text="new ${classname}">
               <annotation datatype="java.lang.Long" key="extent" value="${extent}" />
            </ast>
         </ast>
      </ast>
   </ast-root>
and I could only generate FindQuery[] fqueryArray = new FindQuery[];. I still need to add the array size and I am experimenting.

#86 Updated by Dănuț Filimon about 1 year ago

The following template worked:

   <!-- a named array initializer -->
   <ast-root name="named_extent_array" terse="true" ast_class="com.goldencode.p2j.uast.JavaAst" >
      <ast id="0" type="ASSIGN" text="">
         <ast id="0" type="REFERENCE_DEF" text="${classname}">
            <annotation datatype="java.lang.String" key="name" value="${javaname}" />
            <annotation datatype="java.lang.Long" key="extent" value="${extent}" />
         </ast>
         <ast id="0" type="EXPRESSION" text="">
            <ast id="0" type="REFERENCE" text="new ${classname}">
               <annotation datatype="java.lang.Long" key="extent" value="${extent}" />
            </ast>
            <ast id="0" type="LBRACKET" text="[">
               <ast id="0" type="NUM_LITERAL" text="${extent}" />
            </ast>
         </ast>
      </ast>
   </ast-root>
However, I'd like a second pair of eyes to take a look at it before I move forward. Greg/Constantin, will the template above do to generate FindQuery[] fqueryArray = new FindQuery[5]; or is there a way to make it simpler?

#87 Updated by Dănuț Filimon about 1 year ago

The following test:

DEFINE BUTTON Btn_OK AUTO-GO
    LABEL "OK" .

DEFINE BUTTON Btn_Cancel AUTO-GO
    LABEL "CANCEL" .

DEFINE TEMP-TABLE tt1 FIELD f1 AS INTEGER.

DEFINE FRAME gdialog
   Btn_OK at row 8.67 col 2
   Btn_Cancel at row 8.67 col 21
   with view-as dialog-box keep-tab-order
      side-labels no-underline three-d scrollable
      DEFAULT-BUTTON Btn_OK cancel-button Btn_Cancel widget-id 100.

ON CHOOSE OF Btn_OK IN FRAME gdialog DO:
    DEF VAR firstVal AS INT INITIAL 1.
    DEF VAR lastVal AS INT INITIAL 3.
    DEF VAR i AS INT INITIAL 1.
    DEF VAR iCount AS INT.

    iCount = 0.
    REPEAT i = firstVal TO lastVal:
        FIND FIRST tt1 WHERE tt1.f1 = i NO-ERROR.
        IF AVAIL(tt1) THEN DO:
            MESSAGE "found".
        END.
    END.
END.

view frame gdialog.
converts to
   @LegacySignature(type = Type.MAIN, name = "test4.p")
   public void execute()
   {
      externalProcedure(Test4.this, new Block((Body) () -> 
      {
         gdialogFrame.openScope();
         RecordBuffer.openScope(tt1);
         registerTrigger(new EventList("CHOOSE", gdialogFrame.widgetBtnOk()), Test4.this, () -> new Trigger()
         {
            integer firstVal = UndoableFactory.integer((long) 1);

            integer lastVal = UndoableFactory.integer((long) 3);

            integer i = UndoableFactory.integer((long) 1);

            integer iCount = UndoableFactory.integer();

            FindQuery[] fqueryArray = new FindQuery[1];

            fqueryArray[0] = new FindQuery();

            public void body()
            {
               iCount.assign(0);

               ToClause toClause0 = new ToClause(i, firstVal, lastVal);

               repeatTo("loopLabel0", toClause0, new Block((Body) () -> 
               {
                  fqueryArray[0].initialize(tt1, "tt1.f1 = ?", null, "tt1.recid asc", new Object[]
                  {
                     i
                  }).silentFirst();

                  if (tt1._available())
                  {
                     message("found");
                  }
               }));
            }
         });

         gdialogFrame.view();
      }));
   }
which doesn't compile.

#88 Updated by Dănuț Filimon about 1 year ago

The solution would be to move fqueryArray[0] = new FindQuery(); in the body(), emit the FindQuery normally without considering the array or simply not emitting it at all.

#89 Updated by Greg Shah about 1 year ago

will the template above do to generate FindQuery[] fqueryArray = new FindQuery5; or is there a way to make it simpler?

This approach is good as proposed. Although, I guess you may not need it now.

#90 Updated by Ovidiu Maxiniuc about 1 year ago

I was thinking about fqueryArray[0] = new FindQuery(); initialization. To have a more compact code, we could emit it like this:

      FindQuery[] fqueryArray = new FindQuery[] {
          new FindQuery(),
          new FindQuery(),
      }
if the number of queries is small (let's say 5?). Then for a larger number we may want to initialize the array in a loop, like this:
      FindQuery[] fqueryArray = new FindQuery[];
      // ...
      for (int k = 0; k < fqueryArray.length; k++) {
          fqueryArray = new FindQuery();
      }

However, the chances are that in a big procedure, the queries are executed on different if or switch branches or never executed because of quick or premature return statements (or exception/error conditions). In these cases the initializations would become ballast. In this case the better solution is to create them lazily, uindividually, as close to the usage point.

#91 Updated by Dănuț Filimon about 1 year ago

Ovidiu Maxiniuc wrote:

I was thinking about fqueryArray[0] = new FindQuery(); initialization. To have a more compact code, we could emit it like this:[...]if the number of queries is small (let's say 5?). Then for a larger number we may want to initialize the array in a loop, like this:[...]

However, the chances are that in a big procedure, the queries are executed on different if or switch branches or never executed because of quick or premature return statements (or exception/error conditions). In these cases the initializations would become ballast. In this case the better solution is to create them lazily, uindividually, as close to the usage point.

Creating the FindQuery as close to the usage point is a great idea. I think it would be the best to generate it at the furthest block that is not a looping inner block. So if I have two looping inner blocks, it will look for a parent block that can allow the FindQuery to be emitted there.

#92 Updated by Dănuț Filimon about 1 year ago

I've made some progress, but I will have to refactor all of the changes for the FindQuery. If I am using an array to define the instances, even if just one, it will make things a little bit simpler and I can go ahead and remove FindQuery naming (fquery0..n). I have the templates in place and it partially works, but the only issue is the conflict of the array definition with the initial variables that are being emitted. Somehow I can't generate the array without emitting the variables, even if the array being created is not dependent on those variables. I will have to look into this more and check where's the problem.

#94 Updated by Dănuț Filimon about 1 year ago

Constantin mentioned that I could count the number of FindQueries for each top level block and emit the array only when necessary, I looked into writing a set of rules for this but I am stuck at the ascent rule for adding a fquery-counter annotation. I am working on getting this running.

#95 Updated by Dănuț Filimon about 1 year ago

Current rule-set:

<rule-set input="tree">
   <worker class="com.goldencode.p2j.uast.ProgressPatternWorker" namespace="prog" />

   <variable name="queue"       type="java.util.Deque" />

   <init-rules>
      <rule>queue = create("java.util.ArrayDeque")</rule>
   </init-rules>

   <walk-rules>
      <rule>(evalLib("isTopLevelInternalNode", this)        or
             (this.type == prog.block                       and
              #(boolean) this.getAnnotation("toplevel")))
            <action>queue.push(#(long) 0)</action>
      </rule>

      <rule>
         not queue.isEmpty() and
         (type == prog.statement                                               and
         (downPath(this, prog.kw_find, prog.record_phrase)                    or
          downPath(this, prog.kw_find, prog.kw_last)                          or
          downPath(this, prog.kw_find, prog.kw_prev)                          or
          downPath(this, prog.kw_find, prog.kw_next)                          or
          downPath(this, prog.kw_find, prog.kw_first))) or
          (type == prog.func_logical and getNoteLong("oldtype") == prog.kw_can_find)
         <action>queue.push(#(long) queue.pop() + 1)</action>
      </rule>
   </walk-rules>

   <ascent-rules>
      <!-- the following rule is wrong -->
      <rule> not queue.isEmpty()
            (evalLib("isTopLevelInternalNode", this)        or
             (this.type == prog.block                       and
              #(boolean) this.getAnnotation("toplevel")))
            <action>copy.putAnnotation("fquery-counter", #(long) queue.pop())</action>
      </rule>
   </ascent-rules>

</rule-set>

#96 Updated by Dănuț Filimon about 1 year ago

I managed to get a working version for #9078-95 and started working on a version where it uses an array in case too many FindQuery instances are defined. But I am facing an issue again and again, for the case below:

   @LegacySignature(type = Type.CONSTRUCTOR, name = "ConstructorTest2")
   public void __constructorTest2_constructor__()
   {
      FindQuery fquery0 = new FindQuery();
      FindQuery fquery1 = new FindQuery();
      FindQuery fquery2 = new FindQuery();
      FindQuery fquery3 = new FindQuery();
      FindQuery fquery4 = new FindQuery();
      FindQuery fquery5 = new FindQuery();
      FindQuery fquery6 = new FindQuery();
      FindQuery[] fqueryArray = new FindQuery[7];

      internalProcedure(ConstructorTest2.class, this, "ConstructorTest2", new Block((Body) () -> 
      {
         __lang_BaseObject_constructor__();

         AdaptiveQuery query0 = new AdaptiveQuery();
         forEach(query0, "loopLabel0", new Block((Init) () -> 
         {
            query0.initialize(tt1, ((String) null), null, "tt1.recid asc");
         }, 
         (Body) () -> 
         {
            {
               fquery0.initialize(tt2, ((String) null), null, "tt2.recid asc").first();
               fquery1.initialize(tt2, ((String) null), null, "tt2.recid asc").last();
               fquery2.initialize(tt2, ((String) null), null, "tt2.recid asc").current();
               fquery3.initialize(tt2, ((String) null), null, "tt2.recid asc").first();
               fquery4.initialize(tt2, ((String) null), null, "tt2.recid asc").last();
               fquery5.initialize(tt2, ((String) null), null, "tt2.recid asc").current();
               fquery6.initialize(tt2, ((String) null), null, "tt2.recid asc").first();
            }
         }));
      }));
   }
It does not matter if the array is generated anywhere else (walk rules, post rules, ascent rules)... The moment I get rid of the fquery0...n and those are no longer constructed, the array can no longer be built. I thought that this is because at the time I am creating the array in ascent, the "instvarid" is not present, so I used a queue to store the ids and create the array at the end but it did not help. I have no lead on how to get the array to generate correctly when the separate constructors should not be used, I tried experimenting with ids/control points and I am not sure how to continue.

#97 Updated by Dănuț Filimon about 1 year ago

Dănuț Filimon wrote:

I managed to get a working version for #9078-95 and started working on a version where it uses an array in case too many FindQuery instances are defined. But I am facing an issue again and again, for the case below:
[...] It does not matter if the array is generated anywhere else (walk rules, post rules, ascent rules)... The moment I get rid of the fquery0...n and those are no longer constructed, the array can no longer be built. I thought that this is because at the time I am creating the array in ascent, the "instvarid" is not present, so I used a queue to store the ids and create the array at the end but it did not help. I have no lead on how to get the array to generate correctly when the separate constructors should not be used, I tried experimenting with ids/control points and I am not sure how to continue.

The only way I found to avoid this is to check each time if an array should be generated when I also generate an array entry.

I am currently investigating how to place the array entry as a child before the block that caused it to generate (without breaking the order of other constructs).

#98 Updated by Dănuț Filimon about 1 year ago

  • Status changed from WIP to Review
  • % Done changed from 70 to 100
  • reviewer Constantin Asofiei, Ovidiu Maxiniuc added

Committed 9078b/15974, Added rules to count the number of FindQuery instances in a top level block, but also added FindQuery array for the case when we might hit the local variable definition limit.

Constantin/Ovidiu, please review the counting and the FindQuery array approach.

I took into account moving the array variable assignment to the initialization block which will make this implementation much cleaner, the change is as simple as changing:

<action>elocation = ref.getAnnotation("instvarid")</action>
to
<action>elocation = ref.getAnnotation("controlid")</action>
But I'd like to get your opinion on this.

Currently this moves the definition of FindQuery entries to the first looping inner block, but it should be possible to move it to the furthest looping inner block.

#99 Updated by Dănuț Filimon about 1 year ago

Rebased 9078b to latest trunk/16003, the branch is now at revision 16008.

#100 Updated by Ovidiu Maxiniuc about 1 year ago

I looked at 9078b / r16008.

The code looks pretty much solid. But it's difficult to spot all possible issues only by comparing the files in meld. Serious regression tests is required. The code is expected to be changed (in many places), the individual locations of these queries must be analysed manually.

Here are the issues I could spot:
  • find_processing.rules:
    • lines 94-97 should be indented one more column since they are OR -ed with the downPath() at line 93.
    • also related to above lines: there is no handling for current and unique find s. Shouldn't we handle them the same?
  • database_general.rules
    • lines 272-279: this is a side-effect of a function so the behaviour might not be expected. It should be put right near the function calls, in query_associations.rules.
  • database_access.rules
    • line 2191: embedded comments within tag text is not pretty well supported in TRPL. Therefore I think the section after the comment (line 2192) is probably not processed by the TRPL interpreter.
  • FindQuery.java
    • lines 230, 835, field nested is not in use. Can we drop it?

Due to nature of the TRPL language, there might be other issues I fail to spot. A second pair of eyes from Constantin would be welcomed.

#101 Updated by Dănuț Filimon about 1 year ago

Ovidiu Maxiniuc wrote:

I looked at 9078b / r16008.

The code looks pretty much solid. But it's difficult to spot all possible issues only by comparing the files in meld. Serious regression tests is required. The code is expected to be changed (in many places), the individual locations of these queries must be analysed manually.

Here are the issues I could spot:
  • find_processing.rules:
    • lines 94-97 should be indented one more column since they are OR -ed with the downPath() at line 93.
    • also related to above lines: there is no handling for current and unique find s. Shouldn't we handle them the same?
  • database_general.rules
    • lines 272-279: this is a side-effect of a function so the behaviour might not be expected. It should be put right near the function calls, in query_associations.rules.
  • database_access.rules
    • line 2191: embedded comments within tag text is not pretty well supported in TRPL. Therefore I think the section after the comment (line 2192) is probably not processed by the TRPL interpreter.
  • FindQuery.java
    • lines 230, 835, field nested is not in use. Can we drop it?

Due to nature of the TRPL language, there might be other issues I fail to spot. A second pair of eyes from Constantin would be welcomed.

I addressed the review in 9078b/16009, current and unique find s are already handled by the following rule:

+      <!-- Count all FIND and CAN-FIND statements -->
+      <rule>not blockQueue.isEmpty()                                                     and
+            (type == prog.statement                                                      and
+             (downPath(this, prog.kw_find, prog.record_phrase)                           or  <-- here
+              downPath(this, prog.kw_find, prog.kw_last)                                 or
+              downPath(this, prog.kw_find, prog.kw_prev)                                 or
+              downPath(this, prog.kw_find, prog.kw_next)                                 or
+              downPath(this, prog.kw_find, prog.kw_first)))                              or
+            (type == prog.func_logical and getNoteLong("oldtype") == prog.kw_can_find)
+         <action>...</action>
+      </rule>

After taking a closer look at the asts, the KW_* and RECORD_PHRASE are at the same depth so downPath(this, prog.kw_find, prog.kw_*) is never reached. I will also commit this.

#102 Updated by Ovidiu Maxiniuc about 1 year ago

I think downPath(this, prog.kw_find, prog.record_phrase) will cover all FIND statements. For example, a FIND FIRST ... will have the following AST:

   <ast text="statement" type="STATEMENT">
      <ast text="find" type="KW_FIND">
        <ast text="first" type="KW_FIRST"/>
        <ast text="record phrase" type="RECORD_PHRASE">
So the rest of OR -ed conditions in that rule are superfluous.

#103 Updated by Dănuț Filimon about 1 year ago

I committed 9078b/16010 to address #9078-101 and #9078-102.

#104 Updated by Dănuț Filimon about 1 year ago

Artur found a regression while converting a customer application, currently investigating.

#105 Updated by Dănuț Filimon about 1 year ago

Dănuț Filimon wrote:

Artur found a regression while converting a customer application, currently investigating.

I fixed the issue in 9078b/16011.

#106 Updated by Ovidiu Maxiniuc about 1 year ago

I looked at the fix in 16011. The changes are correct.

#107 Updated by Dănuț Filimon about 1 year ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

There's a scenario when using a DO block as a loop and there is no "instvarid" to attach the array, this causes the conversion to fail. I tried to move the array entry initialization to the top level block, but this caused the entry to be outside the trigger body in a similar scenario.

When the looping inner block looks like this:

                    <ast col="22" id="266287972640" line="30" text="then" type="KW_THEN">
                      <annotation datatype="java.lang.String" key="peerid-byrule" value="convert/control_flow.rules:735"/>
                      <annotation datatype="java.lang.Long" key="peerid" value="730144440472"/>
                      <ast col="0" id="266287972642" line="0" text="inner block" type="INNER_BLOCK">
                        <annotation datatype="java.lang.String" key="loop-byrule" value="annotations/block_properties.rules:566"/>
                        <annotation datatype="java.lang.Boolean" key="error_prop" value="false"/>
                        <annotation datatype="java.lang.Boolean" key="implicit_endkey" value="false"/>
                        <annotation datatype="java.lang.String" key="reasons-byrule" value="annotations/block_properties.rules:278"/>
                        <annotation datatype="java.lang.Long" key="reasons" value="0"/>
                        <annotation datatype="java.lang.Boolean" key="loop" value="true"/>
                        <annotation datatype="java.lang.Long" key="block_par_id" value="730144440473"/>
                        <annotation datatype="java.lang.Long" key="peerid" value="730144440475"/>
                        <annotation datatype="java.lang.String" key="error_prop-byrule" value="annotations/block_properties.rules:1146"/>
                        <annotation datatype="java.lang.String" key="translevel-byrule" value="annotations/block_properties.rules:277"/>
                        <annotation datatype="java.lang.String" key="implicit_endkey-byrule" value="annotations/block_properties.rules:1145"/>
                        <annotation datatype="java.lang.String" key="block_type-byrule" value="annotations/block_properties.rules:556"/>
                        <annotation datatype="java.lang.Long" key="translevel" value="149"/>
                        <annotation datatype="java.lang.String" key="block_type" value="DO_TO"/>
                        <annotation datatype="java.lang.String" key="implicit_error-byrule" value="annotations/block_properties.rules:1144"/>
                        <annotation datatype="java.lang.String" key="interactive-byrule" value="unreachable/unreachable.xml:996"/>
                        <annotation datatype="java.lang.Boolean" key="integrated_complex_block" value="true"/>
                        <annotation datatype="java.lang.Boolean" key="loopnext" value="true"/>
                        <annotation datatype="java.lang.Boolean" key="implicit_error" value="false"/>
                        <annotation datatype="java.lang.String" key="block_par_id-byrule" value="convert/control_flow.rules:2337"/>
                        <annotation datatype="java.lang.Boolean" key="toplevel" value="false"/>
                        <annotation datatype="java.lang.String" key="toplevel-byrule" value="annotations/block_properties.rules:563"/>
                        <annotation datatype="java.lang.String" key="integrated_complex_block-byrule" value="annotations/label_manufacturing.rules:174"/>
                        <annotation datatype="java.lang.String" key="peerid-byrule" value="convert/control_flow.rules:2329"/>
                        <annotation datatype="java.lang.String" key="loopnext-byrule" value="annotations/block_properties.rules:649"/>
                        <annotation datatype="java.lang.Boolean" key="interactive" value="false"/>
I have no instvarid annotation.

#108 Updated by Dănuț Filimon about 1 year ago

  • Status changed from WIP to Review
  • % Done changed from 90 to 100

Committed 9078b/16012. In case of a DO block that is also a loop, ensure that a block/inner block with "instvarid" is used to generate the array extent variable assign.

#109 Updated by Ovidiu Maxiniuc about 1 year ago

The 9078b/16012 seems OK.

#110 Updated by Constantin Asofiei about 1 year ago

Danut, does AdaptiveFind still emit properly? This is the FIND FIRST. REPEAT: FIND NEXT. END. pattern, if I'm not mistaken.

#111 Updated by Dănuț Filimon about 1 year ago

Constantin Asofiei wrote:

Danut, does AdaptiveFind still emit properly? This is the FIND FIRST. REPEAT: FIND NEXT. END. pattern, if I'm not mistaken.

Yes, it emits properly. AdaptiveFind is emitted a bit sooner.

#112 Updated by Dănuț Filimon about 1 year ago

Rebased 9078b to latest trunk/16028, the branch is now at revision 16037.

#113 Updated by Dănuț Filimon about 1 year ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

I've found an issue while converting ETF:

EXPRESSION EXECUTION ERROR:
---------------------------
tw.graft("anon_array", copy, location, "classname", "Object", "extent", string(numImmediateChildren))
   ^  { Invalid parentid:  -1 }
---------------------------
Elapsed job time:  00:06:53.286
ERROR:
com.goldencode.p2j.pattern.TreeWalkException: ERROR!  Active Rule:
-----------------------
      RULE REPORT      
-----------------------
Rule Type :   WALK
Source AST:  [  ] BLOCK/PROCEDURE/BLOCK/INNER_BLOCK/BLOCK/STATEMENT/KW_FIND/RECORD_PHRASE/QUERY_SUBST/ @0:0 {24708946903337}
Copy AST  :  [  ] BLOCK/PROCEDURE/BLOCK/INNER_BLOCK/BLOCK/STATEMENT/KW_FIND/RECORD_PHRASE/QUERY_SUBST/ @0:0 {24708946903337}
Condition :  tw.graft("anon_array", copy, location, "classname", "Object", "extent", string(numImmediateChildren))
Loop      :  false
--- END RULE REPORT ---

    at com.goldencode.p2j.pattern.PatternEngine.run(PatternEngine.java:1100)
    at com.goldencode.p2j.convert.TransformDriver.processTrees(TransformDriver.java:589)
    at com.goldencode.p2j.convert.ConversionDriver.back(ConversionDriver.java:591)
    at com.goldencode.p2j.convert.TransformDriver.executeJob(TransformDriver.java:1001)
    at com.goldencode.p2j.convert.ConversionDriver.main(ConversionDriver.java:1305)
Caused by: com.goldencode.expr.ExpressionException: Expression execution error @1:4 [QUERY_SUBST id=24708946903337]
    at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:275)
    at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:210)
    at com.goldencode.p2j.pattern.PatternEngine.apply(PatternEngine.java:1692)
    at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1578)
    at com.goldencode.p2j.pattern.PatternEngine.processAst(PatternEngine.java:1511)
    at com.goldencode.p2j.pattern.PatternEngine.run(PatternEngine.java:1063)
    ... 4 more
Caused by: com.goldencode.expr.ExpressionException: Expression execution error @1:4
    at com.goldencode.expr.Expression.execute(Expression.java:495)
    at com.goldencode.p2j.pattern.Rule.apply(Rule.java:500)
    at com.goldencode.p2j.pattern.Rule.executeActions(Rule.java:751)
    at com.goldencode.p2j.pattern.Rule.coreProcessing(Rule.java:717)
    at com.goldencode.p2j.pattern.Rule.apply(Rule.java:537)
    at com.goldencode.p2j.pattern.RuleContainer.apply(RuleContainer.java:597)
    at com.goldencode.p2j.pattern.RuleSet.apply(RuleSet.java:98)
    at com.goldencode.p2j.pattern.RuleContainer.apply(RuleContainer.java:597)
    at com.goldencode.p2j.pattern.RuleSet.apply(RuleSet.java:98)
    at com.goldencode.p2j.pattern.AstWalker.walk(AstWalker.java:262)
    ... 9 more
Caused by: java.lang.IllegalArgumentException: Invalid parentid:  -1
    at com.goldencode.p2j.pattern.TemplateWorker$Template.graftAt(TemplateWorker.java:370)
    at com.goldencode.p2j.pattern.TemplateWorker$Template.graft(TemplateWorker.java:318)
    at com.goldencode.expr.CE21351.execute(Unknown Source)
    at com.goldencode.expr.Expression.execute(Expression.java:398)
    ... 18 more

I've done the following:
  1. I've extracted the problematic procedures where I found similar source ASTs, but I found no issues while converting them.
  2. I converted the full file after adding all dependencies and it still converted properly
What I found instead:
  1. An array which initializes 57 elements, but only uses 35. It seems I am counting more FindQueries than I need to. The problem is that i need to know if the FindQuery can be attached in the annotations phase, but I do not have the right annotations in place to check this (I have to check if I have a top level block that it can attach to, not just exists).

#114 Updated by Dănuț Filimon about 1 year ago

I've managed to fix the issue, I've also noticed that CAN-FINDs were ignored and AdaptiveFinds were also counted. I'll do a cleanup and a small test and commit my changes soon.

#115 Updated by Dănuț Filimon about 1 year ago

Dănuț Filimon wrote:

I've managed to fix the issue, I've also noticed that CAN-FINDs were ignored and AdaptiveFinds were also counted. I'll do a cleanup and a small test and commit my changes soon.

Committed the fix for #9078-113 and #9078-114 to 9078b/16038.

#116 Updated by Dănuț Filimon about 1 year ago

  • Status changed from WIP to Review
  • % Done changed from 90 to 100

I added 9078b to my ETF conversion queue, it will take a while to get through it.

#117 Updated by Dănuț Filimon about 1 year ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

Found the issue from #9078-113 again, but I got a reliable reproduction this time. It seems that when I use the array version, I end up looking the id I need to attach of an addComponent() call. I thought I kept the qryid assigned properly in all scenario, but I am missing one.

#118 Updated by Dănuț Filimon about 1 year ago

  • % Done changed from 90 to 100
  • Status changed from WIP to Review

Fixed #9078-113 in 9078b/16039. We did not emit the find query as a variable when we had a block in the queue, but it was not the block we were into. At the same time, the find query counter should be block specific.

#119 Updated by Dănuț Filimon about 1 year ago

DO blocks are quite tricky, those blocks do not have an instvarid and in some cases I can get out of the expected block with a getAncestor call. I saw no other solution that using the controlid in those cases. Committed 9078b/16040.

#120 Updated by Dănuț Filimon about 1 year ago

I did a ChUI conversion and the project built with no issues, I noticed both the array and variable FindQuery types being used.

#121 Updated by Dănuț Filimon 11 months ago

Rebased 9078b to latest trunk/16122, the branch is now at revision 16134.

#122 Updated by Alexandru Lungu 11 months ago

  • Related to Feature #10436: Detect Sequential Data Access and Prefetch Records from the Database added

Also available in: Atom PDF