Friday, 17 December 2021

Extending the Priority Cross-referencer

The other day I added some new code to a procedure that I had written several months ago. The purpose of the code was to sum the quantity of a raw material found in the kititems of the work orders belonging to a given order line. The actual query for a given line worked correctly when I checked it in WINDBI, but inside the procedure it didn't work properly. The code itself was within a cursor loop and it was clear that the loop did not complete.

Eventually I realised that I had left out a few of the commands necessary for a cursor, primarily missing checking RETVAL after fetching a new tuple (to see whether there are more tuples). The procedure worked correctly once I added the missing commands. 

Just to be clear, the complete order of commands should be

  1. Declare
  2. Open
  3. Retval 
  4. Fetch
  5. Retval
  6. Loop
  7. Close
Once I had the procedure working, I decided to add more checks to PrioXRef. 'Retval' and 'Loop' differ from the other cursor commands in that the others are always followed by the cursor name, whereas 'Retval' and 'Loop' aren't. Just to complicate matters, 'Retval' is a variable (it has the colon prefix).

After some thinking, I figured out that I needed to store cursor names in a stack (specifically, after 'declare'), so that 'Retval' and 'Loop' can reference the current cursor name. 'Close' 'pops' the current cursor off the stack. This part eventually worked well but the analysis of the nodes in the parse tree for a cursor became more complicated, primarily because 'Retval' has to appear twice in specific places. After working on this for a while, I wrote new code that checks that all seven commands appear in the correct order. This code can't check in a strictly linear fashion: let's say that the first 'retval' is missing; the third node in the cursor tree will be 'fetch' which is not the third identifier ('retval'), but it is the fourth identifier. 

I've only checked the new version against a simple, toy, procedure, but next week I'll check the version against real code with multiple cursors, some of which are cursors within cursors (this is the reason for the stack).

Edit from 18/12/21: It seems that I was premature in stating that LOOP had been handled. I woke up this morning, realising that there are cases of LOOP that I had not handled: in about 20% of the procedures that I write, there are multiple LOOP statements for the same cursor. Very occasionally there is a procedure that has a LOOP with no cursor - this generally happens when iterating over a series of dates. Both of these cases would break what I wrote yesterday.

Edit from 03/01/22: Unfortunately I have to rewrite the code for RETVAL. Quite often I write code with a cursor (or even two) embedded within a cursor. Whilst the first cursor would have 'goto 500 where :retval <= 0' after the 'open' command, the internal cursor would have 'loop 100 where :retval <= 0' if there is only one cursor. There wouldn't be a problem if there were two internal cursors as presumably the second has to execute even if the first fails (and so after 'open' would be written 'goto <label> where :retval <= 0). It looks like I am going to have to write some kind of state machine with much more detail than I have done so far (e.g. the only cursor command that can come after 'declare' is 'open'; anything else is a mistake).