Showing posts with label Version control. Show all posts
Showing posts with label Version control. Show all posts

Sunday, 12 November 2023

Version control for screen triggers

Two and a half years ago, I developed a system for version control of procedures. This system has proved invaluable at times when a change to a procedure did not not give the desired output; I could revert the code to the previous version thus restoring the status quo. At one time I extended the system to work with interface code - this was very simple. For some time now, I have been wanting to extend the system to provide version control for screen triggers, but this is not so simple.

In the same way that a procedure can have several SQLI stages, a screen/form can have several triggers. But as opposed to procedures where every SQLI stage has a different value in the PROGRAMS table, triggers are managed in a similar albeit different manner, by means of the FORMTRIG table that has both a pointer to the parent form (e.g. ORDERS) and a pointer to the given trigger. 

Originally I thought that I would have to develop a completely new system for form triggers, albeit influenced by the procedures code. But when I devoted some serious thought to the topic, I realised that all I need do was add a new field to the table that manages the versions; this field would be a pointer to the given trigger that along with the pointer to the parent form would completely identify the trigger. Obviously the code that saves a version would have to be written, but this would be very similar to the code that saves a version of a procedure or interface.

Once I had this insight, there wasn't very much that I could do to advance the subject, as the new field would have to be part of the primary key (U) of the manager table that would mean having to add the field to the appropriate form, which is something that I don't do during regular work hours. Fortunately Friday afternoon is a time when no one ever seems to be connected to Priority, so for me it's an ideal time to work on form code. 

First I added the new field, then I updated all the existing tuples in the table to have the value -10 in this field; every tuple has to have a value, but it is meaningless when the tuple is for a procedure. Then I had to add this field (and its value) to the existing screen, then prepared all the forms. Then I updated the code for saving procedure versions to include this new field and saved this new version: the form displayed it correctly. Why -10? 0 would appear to be a better value, but I am wary of saving 0 in a key field. There are five predefined triggers (four search triggers and CHOOSE-FIELD) that have values -5 to -1; as I didn't want a collision with these values, I chose a different number. It occurred to me later that a tuple for a procedure couldn't possibly have the same EXEC value as a tuple for a trigger, so I could have used -1 without problem.

Then I had to start work with the trigger code. First I defined a new form for displaying versions that is almost exactly the same as the existing form albeit with different values for the EXEC and TRIG fields. This form is a son of the FTRIG form. Once I had the form defined, I could define the interface for saving a new version, and once I had the interface I could define the required procedure. This is almost a chicken and egg problem, but there is a way through the convoluted path.

Debugging the procedure was a different story to the simple steps that I had taken so far. The first - and relatively minor - problem was that I had neglected to define correctly the TRIG field in the new form. Once this was fixed, I could save new versions of each trigger, but the text - the point of the entire exercise - was not being saved. In true debugging fashion, I took the dog for a walk and considered my options. It turned out that the procedure was saving the code text in GENERALLOAD.TEXT, but the interface was expecting the text in TEXT2. A hard to find but easy to correct bug. It then occurred to me that I could improve the system by saving the date of the trigger as the version date; this saves me some work when saving existing triggers, although of course it won't make any difference when saving a new version of a trigger.

An ounce of prevention is worth a pound of cure.

Wednesday, 28 July 2021

Version control for Priority procedures (2)

I've been working a certain amount recently with load interfaces, causing me to bemoan that there's no easy way of documenting them. As opposed to procedure steps (that are stored in tables PROGRAMS and PROGRAMSTEXT), these are in tables LOAD and LOADTEXT. Despite this, it was fairly easy to extend the SAVEPROGVERSION that I wrote about a few months ago to include load interface code.

There are two new snippets that have to be inserted into the procedure's text; the first is at the beginning

:LOAD = 0; :TYPE = '\0'; /* 28/07/21: Get interface data */ SELECT EXEC.EXEC, EXEC.TYPE INTO :LOAD, :TYPE FROM EXEC, PROGRAMS WHERE PROGRAMS.EXECRUN = EXEC.EXEC AND PROGRAMS.PROG = :$.PRG;
whereas the second part is towards the end: I have 'bolded' the new lines
GOTO 1 WHERE :TYPE = 'L'; /* Interface */ INSERT INTO GENERALLOAD (LINE, RECORDTYPE, TEXT) SELECT 3 + SQL.LINE, '4', TEXT FROM PROGRAMSTEXT WHERE PROG = :$.PRG; GOTO 2; LABEL 1; INSERT INTO GENERALLOAD (LINE, RECORDTYPE, TEXT) SELECT 3 + SQL.LINE, '4', TEXT FROM LOADTEXT WHERE LOAD = :LOAD; LABEL 2; EXECUTE INTERFACE 'TEST_PROGVERSION', SQL.TMPFILE, '-L', :$.GEN;

Monday, 15 March 2021

Version control for Priority procedures

Someone asked how to maintain previous versions of procedures written in Priority; I answered that I had written a procedure that outputs the text to a text file that I store in a given directory. Someone else pointed out that Priority does have support for procedure versions, but when I looked into this, it appeared that this support is connected directly to a module that prepares 'update' files. These files are used when one develops on one machine and wants to install on another machine. I don't think that this was what the original question was about.

But this reply started me thinking: how hard would it be to develop a table (or two) that stores program text? The answer: fairly easy. I needed to define a table, a form and an interface. Procedures are maintained via a form called EPROG that is based on the EXEC table; this form has a child form called PROG that presents the procedure stages, and the text for each stage is shown in a form called PROGTEXT (not that this is important). The base table of PROG is PROGRAMS, whose primary key (or in Priority-speak, its auto-unique A index) is PROG. So I would need to define a table whose key included PROG.

The definition of my new table TEST_PROGVERSION is as follows

Field name Type Description Key
TPV Int Auto-increment A
PROG Int Pointer to procedure U
VERSION Int Text version U
CURDATE Date Date of version

On the basis of this table, I defined a form with the same name; this form is a child form of the PROG form, and PROG in my form is equal to :$$.PROG, i.e. the PROG is the parent form. TPV and PROG are hidden fields, and the form is sorted by VERSION descending, i.e. the newest version is first. Originally I defined the form to be query only (i.e. type Q) but this led to problems with the interface, so the form is now of type N (no deletions) and the two visible fields are read-only.

After I had a table and form, I used the built-in procedure for defining a text form; this created a table and form with the name TEST_PROGVERSIONTEXT and added this new form as a child form to the TEST_PROGVERSION form.

An interface has to be designed in order to insert data into these tables; this is required because of the text form that has special requirements, otherwise I could simply insert the required data into the tables. The interface has to be four levels deep: first EPROG, then PROG, then TEST_PROGVERSION and finally TEST_PROGVERSIONTEXT. 

How does one store a version? I wrote a procedure that appears as a direct activation from form PROG. Here it is
LINK PROGRAMS TO :$.PAR; SELECT PROG INTO :$.PRG FROM PROGRAMS WHERE PROG > 0; UNLINK PROGRAMS; :VERSION = 0; SELECT MAX (VERSION) INTO :VERSION FROM TEST_PROGVERSION WHERE PROG = :$.PRG; :PAR1 = '$'; LINK GENERALLOAD TO :$.GEN; ERRMSG 1 WHERE :RETVAL <= 0; INSERT INTO GENERALLOAD (LINE, RECORDTYPE, TEXT6) SELECT 1, '1', EXEC.ENAME FROM EXEC, PROGRAMS WHERE EXEC.EXEC = PROGRAMS.EXEC AND PROGRAMS.PROG = :$.PRG; INSERT INTO GENERALLOAD (LINE, RECORDTYPE, INT1) VALUES (2, '2', :$.PRG); INSERT INTO GENERALLOAD (LINE, RECORDTYPE, INT1, DATE1) VALUES (3, '3', :VERSION + 1, SQL.DATE8); INSERT INTO GENERALLOAD (LINE, RECORDTYPE, TEXT) SELECT 3 + SQL.LINE, '4', TEXT FROM PROGRAMSTEXT WHERE PROG = :$.PRG; EXECUTE INTERFACE 'TEST_PROGVERSION', SQL.TMPFILE, '-L', :$.GEN; GOTO 1 WHERE NOT EXISTS (SELECT 1 FROM ERRMSGS WHERE USER = SQL.USER AND TYPE = 'i'); EXECUTE WINACTIV '-R', 'INTERFACEERR'; LABEL 1;
UNLINK GENERALLOAD;
The entire process would have taken about twenty minutes, had I foreseen the problem of defining the new form not of type Q but of type N. A further waste of time happened when I looked at the text that was stored in the text form: it was aligned to the right. I tried to insert a line into the GENERALLOAD table to correct this, but I must have forgotten the correct commands. In the end, I realised that I can leave the text as it is, and if I need it left aligned, then I can use the command in the in-built HTML editor of the form that performs the switch and keeps the result.