Saturday, September 28, 2019

Understanding the UNLINK command in Priority

I get the feeling that 'UNLINK' is one of the most misunderstood commands in Priority (and I'm not insinuating that I understand it perfectly).

First, what does LINK do? The LINK mechanism creates a temporary copy of a given database table. And what does UNLINK do? The UNLINK command stores the temporary file in the specified (linked) file and undoes the link. More importantly, The linked file is initially empty of data. All subsequent operations that refer to the original table are actually executed upon that temporary file, until the UNLINK command is encountered. You cannot link the same table more than once prior to an UNLINK. If you do, the second (and any subsequent) LINK to that table will return a value of –1.

Developers use the LINK/UNLINK commands frequently in two main contexts. The most frequent use is in procedures, where a linked table (most frequently STACK4) is used to store data collected during the procedure; this data is then passed on to a connected report which uses the linked table. One soon discovers that there is no need to call UNLINK at the end of the procedure.

In more complicated procedures, consisting of several steps, the linked table might be referenced in several of those steps, in which case it is prudent to unlink the table at the end of each step and relink it at the beginning of the next step. Not calling UNLINK will lead to problems as I mentioned in an earlier tip.

The second most frequent use is again in procedures where data is stored prior to it being used to update data in a screen by means of an interface. Again, one discovers that the procedure will work correctly even if the UNLINK is omitted.

So what's the problem? One can (and one frequently does) write interface code which is run in a screen trigger. There are several important (and not particularly documented) differences between code which runs in a procedure and code which runs in a trigger. The most obvious difference (in the case of linked files) is that one cannot link a table to an external parameter as one normally does (e.g. LINK STACK4 TO :$.STK) because the concept of an external parameter is inappropriate in the context of a trigger. Instead, one has to create a temporary file (SELECT SQL.TMPFILE INTO :TMPFILE) then link a table to this temporary file (LINK GENERALLOAD TO :TMPFILE). One always checks whether the link succeeds; should the link fail, one can call an error message in a procedure, but in triggers, one normally silently jumps over the interface code when the link fails. As a result, whatever the trigger is supposed to do does not happen.

I have been receiving complaints from a group of users that certain functionality in one screen has disappeared. Every time I check their complaints, I find that the code works. I wasn't listening closely enough: in certain circumstances (which for them is the normal way of working), the code does not work and a certain field does not get updated. I could see where the specific code was but it seemed correct to me. In order to help me further understand the problem, I inserted debugging code, which would give me the result of the SELECT SQL.TMPFILE operation and the subsequent LINK operation. Once I had this, the problem became clearer.

The SELECT statement worked correctly, but the LINK was returning a value of -1, which is explained as you cannot link the same table more than once prior to an UNLINK. The penny dropped: linking Generalload to the temporary file was failing because Generalload was already linked. But where?

My initial reaction was to alias the generalload table (LINK GENERALLOAD TB6 TO :TMPFILE) and use the alias for all insert commands within the trigger. This caused the update to work properly. Thinking about this further, I realised that there must be at least one other trigger for this screen which does not UNLINK generalload. I looked at all the triggers for this screen and discovered to my horror that there were five or six cases where I had neglected to UNLINK generalload after using it in interface code. Today I had the opportunity to add all the missing UNLINKs.

The reason why one can get away with not using UNLINK in a procedure is that the code is called only once. A screen activates multiple triggers and so it is essential that every LINK is matched with an UNLINK. Make this a habit to do this always!

It's a shame that the syntax checker does not attempt to check unmatched LINKs. The checker does find several kinds of mistake, but there are a few which it doesn't look for. I have noted somewhere that whilst a repeated declaration for a cursor is noted, not closing the cursor is undetected. Unmatched LINKs fall in the same category of mistake. I get the feeling that the syntax checker was written many years ago, has not been updated and no one knows how to update it.

No comments: