(4GL Build)
FGLBLD is an application generator for xxx which generates 4GL code suitable for use with any x4gl compatible compiler (Informix, 4Js, Querix, Aubit...). It generates code to handle a particular style of interaction which is termed a Simplified Perform Interface or SPI. This is analogous to a customized version of the xxx program xxx.
In the hands of an experienced user, FGLBLD can be used to create a custom application in less than an hour with a complete set of default help messages, multiple pop-up facilities to allow the user to choose a value from a list, and a report which at least gives all the information stored in the table. Unlike some other products available, it is possible to have more than one SPI included in one program - the functions are named systematically but use the table name as part of the function name.
This document is a user guide to the product. It covers the use of the product, the structure of the code, how to modify the code, how to install the product and what could be improved. It assumes a good working knowledge of 4GL and xxx and a reasonable understanding of shell scripts - not normally a problem if the other requirements are met. It does not hold your hand and take you through every keystroke.
FGLBLD is an application generator for xxx which can generate high-quality programs from just 5 pieces of information specified by the programmer (the database, the table, the primary key, the menu title and the basename for the files). All the code used by the generated program is supplied as source code so there are no hidden libraries.
The generated code is easily modifiable; FGLBLD supplies code generators which allow the programmer to create popup windows which allow the user (of the generated program) to select the required input value from a list, and also code which simplifies the validation and display of data from joining tables.
Although it does not eliminate all xxx programming (only the simplest of application would need no improvements beyond reorganizing the layout of the screen form), FGLBLD does speed up the development process greatly.
As mentioned above, an SPI is a Simplified Perform Interface. To understand what is meant by this, it is helpful to understand what the regular xxx interface looks like.
The xxx program is the xxx screen-based transaction processor, which is a fancy way of saying it allows the user to edit the data in the database, using a screen from (a screen layout with additional control information) to allow the user to see what they are doing. It is basically very easy to use: there are six important options (query, next, previous, add, remove, update), six minor options (screen, table, master, detail, current, output), and the exit option. The main menu for xxx is illustrated in Figure 1.
The PERFORM main menu
FIXME: Fig 1. perform.pic
xxx works with one active table at a time (in the figure, the active table is customers), though there may be a number of tables listed in one form. The important options allow the user to work on the current table and to:
The behavior of the add, delete and update options can be partially controlled by information in the ATTRIBUTES and INSTRUCTIONS section of the form. This allows some simple validation to be done, and some types of cross-referencing (lookup, verify joins) can be done as the data is displayed. However, there are many complex types of validation, and cross-references to multiple tables, which cannot be handled in xxx.
The minor options (except Output) are used to control complex forms with several screen layouts and several tables in the form. These are the least satisfactory part of xxx because the user must be trained to understand what is happening to be able to use these options reliably.
Although the complete set of options in xxx is unwieldy, the set of important options plus output and exit are very useful for editing one table at a time, and these options can be understood by most users.
It is possible to provide an xxx program which supplies these options and which allows the programmer to add a variety of extra features, such as more complicated data validation, choosing from lists of possible data values, complicated lookups for data, properly cascaded deletes, and also extra constraints on the user -- even different constraints on different users.
The basic SPI menu offers the eight options shown in Figure 2.
FIXME: Fig 2
"A main menu generated by FGLBLD." fglmenu.pic
The Exit option is the way of terminating the menu and does not need further discussion.
The Query, Next and Previous options allow the user to specify which data they wish to look at and to step through the data. Both Next and Previous can be preceded by a number indicating the number of records to jump over. The Add option (which might be called Insert to be consistent with the other database operations) allows the user to add a new row of data.
The Delete option allows the user to reconsider their decision, but deletes the current row of data when required. The Update option allows the user to edit the current row of data. The Report option allows the user to print the data they are working with, or to file it or see it on the screen.
This basic set of options can be augmented by editing the generated code.
FGLBLD also provides a set of hidden options. These include First and Last to move to either end of a set of records, Goto to move to a particular record in the list, Current to reselect the current row of data (it may have been changed by someone else), Same to re-execute the same query again, and there is normally a shell escape option too.
FGLBLD is a program written in xxx which gives the user a menu-driven system for working with SPIs.
It has options to create an SPI, a pop-up function (which allows the user to choose a value from a list), or a fetch function (which collects a record from the database). It also provides a usable environment in which to edit the code for an SPI, to recompile it or run it.
The standard distribution comes in two flavors depending on whether compiled xxx or Informix-RDS is in use. If the distribution is for compiled xxx, there are just two user-level commands, namely fglbld and de-fluff. If the distribution is for Informix-RDS, you also get a command called mkfglgo.
The main command that users see is fglbld. This is actually a shell script which amends the environment so that the rest of FGLBLD will work correctly and then runs the program that gives the user the menu and so on.
Defluff is a filter which removes certain comments from the code generated by fglbld. These comments indicate how the code could be modified if required, but the comments in, for example, the input validation functions are repeated for every function and are frequently unwanted. Defluff removes these comments.
Mkfglgo is used to create a custom version of fglgo and fgldb. It copies the necessary source code into the current directory (including a makefile) and then compiles both fglgo and fgldb. If required, the set of functions can be extended by the user.
These three scripts are normally installed in one of the standard bin directories on the system: the default directory would be $INFORMIXDIR/bin.
The rest of the software is normally stored in a separate directory system such as the default, /usr/fglbld, though it could be installed under $INFORMIXDIR. The software comprises the xxx executable (or interpretable for an Informix-RDS installation), the code generator scripts, the form files, the help message files, the installation tools and the code templates.
When generating code with FGLBLD, one of the questions the user is asked is the name of the primary key column. It is important to be able to answer this question correctly; if an incorrect answer is given, the behavior of the generated program will be unsatisfactory to the user.
Put simply, the primary key of a table is the column or set of columns which contains a unique identifier for each row of data. A typical example of a primary key is a SERIAL column. When the table is created, the SERIAL column will have nulls disallowed (NOT NULL), and there will be a unique index on the column too. By specifying the value in the SERIAL column in the WHERE clause of a SELECT statement, at most one row of data will be fetched from the database. If there is a row with the matching value stored in the SERIAL column, it will be retrieved; if there is no such row, no rows will be returned. Under no circumstances will more than one row be returned.
Some tables do not have a single-column primary key. For example, a table expressing a relationship between two entities defined in different tables normally has a primary key consisting of a value from the first table and a value from the second table. FGLBLD handles this by using the ROWID in place of a single column -- see the section on `Creating an SPI'.
Versions of xxx prior to 1.10 only had one type of CURSOR and the only operations available on these were OPEN, FETCH and CLOSE. The FETCH operation always fetched the next row. This sort of cursor is not very suitable for moving backwards and forwards through a set of rows of data -- backwards is particularly difficult.
Later versions of added SCROLL CURSORS which support the operations FETCH FIRST, FETCH LAST, FETCH PREVIOUS, FETCH NEXT, FETCH ABSOLUTE, FETCH RELATIVE and FETCH CURRENT as well as OPEN and CLOSE.
At first sight, these seem to be ideal for implementing an SPI; further acquaintance reveals some shortcomings. The main shortcoming is that the list of rows in the cursor cannot be modified after the cursor is opened. This means that if the user deletes a row from the list, there is no way of removing it from the set of rows in the scroll cursor, so if the user moves on to the next item and then comes back, it will look to them as though the row has not been deleted. There are two ways of circumventing this trouble:
After a delete or update or sequence of add operations, reopen the scroll cursor and reposition the user in the list.
Only select the ROWID (or possibly the primary key column(s) for the table) with the scroll cursor. Every time the user changes the current row, use the scroll cursor to fetch the next key value and then select the data from the database again.
The first alternative is clumsy, particularly if the query specified by the user fetches a large number of rows of data. Also, repositioning the user in the list at the same point is impossible to do accurately on a multi-user system because someone else could have been adding or deleting rows while the user was fiddling.
The second alternative requires some complex coding to handle the rows which are no longer retrieved because the original record has been deleted; should the next record which is fetched be FETCH NEXT or FETCH PREVIOUS, and what happens when the first or last record is deleted, or all records are deleted.
A second issue with SCROLL CURSORS arises if the database has a transaction log on it; all SELECT FOR UPDATE statements (and table locks) have to be applied within a transaction. When a transaction is terminated, all currently open cursors are closed. It is not satisfactory to start a transaction when the user starts the program and terminate it when they finish since it definitely reduces the possibility of multiple users accessing the database and can also run into problems with the number of locks applied to the table.
D-Lists are a solution to the problems outlined above. The name is an abbreviation of `doubly-linked list'. They are a set of functions written in C which can maintain arbitrary lists of values. The operations supported on D-Lists include add, update and delete as well as create, destroy and a fetch function which includes all the operations supported by scroll cursors. There are a couple of other utility functions which return the index number of the current row and the total number of rows, and there is a function to empty the list but not destroy it. These operations are completely independent of the database engine and so they are not affected by transaction boundaries.
There is a problem with using D-Lists from xxx, namely that for each type of value to be stored in the D-List, you need a separate set of interface routines. For practical reasons therefore, only four sets of xxx routines have been provided, and they are for integers, strings, decimals and floats.
The decimal and float routines have never been used in anger; it is implausible (but not impossible) to have a primary key based on these types. If a table has a composite primary key (a key consisting of more than one column), it is necessary to use the integer D-List routines to store the ROWID of each row and to use this to retrieve the relevant information. It would be possible to use the ROWID for all operations, but if the primary key is a simple integer (or a SERIAL or DATE column) or a character string, it is more natural to use these instead.
The only disadvantage of using D-Lists is if Informix-RDS is in use; because they are written in C, the D-List routines have to be incorporated into customized versions of fglgo and fgldb. The script mkfglgo is provided with FGLBLD to create these programs.
FGLBLD can be run in either of two ways. Simply typing:
fglbld
runs the program and is the normal way of using it. Alternatively, typing:
fglbld database
will pre-select the specified database. The database can be changed within fglbld; if it is not pre-selected (or if the pre-selected database cannot be opened), it will have to be specified using either the Database option in the main menu or before any code is generated. The main menu is illustrated in Figure 3.
FXIME: Fig 3
"The FGLBLD main menu." mainmenu.pic
There is a single hidden option: `!' provides a shell escape. Throughout FGLBLD itself, the following rules apply:
A lot of the information presented in the next section (creating an SPI) is also applicable to creating a popup function and creating a fetch function.
When the SPI option is chosen, a form is displayed and has to be filled in.
If no database has been specified, another, smaller window will be popped up asking for the name of a database. Until a database has been opened successfully, you cannot proceed further, though an interrupt will take you back to the main menu.
The name of the database should not contain any path; it must be accessible via the environment variable DBPATH. Once a database has been selected, there are a number of details that must be specified, and also a number of options that may be specified. The mandatory items are:
The table name must be specified first; it can be entered directly, or it can be chosen from a list by pressing the F5 key or control-B.
When choosing from a list, you are offered a conditional popup which was originally generated by FGLBLD. When the conditional popup code is entered for the first time, there are no items in its list, so it immediately asks you to specify the criteria for selecting the list of tables. You can specify conditions on the table name or number; you could just hit the ESCAPE key which would select all the tables in the database, or you could specify that the table number should be greater than or equal to one hundred which yields a complete list of user-defined tables, or you could specify some other condition.
The list of tables is displayed using a DISPLAY ARRAY, and you can choose the required table (or view) by hitting ESCAPE with the cursor on the correct row.
Hitting the interrupt key will abandon the selection process and return you to the form so that you can type a table name. When a conditional popup is entered and there are some items in its list, it will offer you a menu with options Query, Same and Exit.
The Query option allows you to enter new criteria for the tables to be shown; the Same option allows you to choose from the same list as before; and the Exit option exits the popup without selecting anything. If the criteria ever returns an empty list of names, you will be shown the menu again.
If the search criteria select more than thirty table names, only the first thirty will be shown; you will have to re-specify the criteria if the required table is not in the list. When the database changes, any previous list of table names is forgotten. This table popup function is also used when creating a popup function or a fetch function.
Once the table has been chosen, the primary key must be specified. The column name can be entered directly, or you can choose from a list by pressing the F5 key. The popup is an unconditional popup this time -- the possible choices of column name are fixed when the table is specified. (This too was originally generated by FGLBLD, but it was modified to take the table number as an argument so that the correct list of columns can be generated automatically.)
If the table's primary key is a composite key, the special value `ROWID' should be entered or chosen. This name must be entered in capital letters. The ROWID is always a valid choice as the primary key, but it is often convenient to use a real column that does not allow nulls and which has a unique index as the primary key. A typical example is a SERIAL column. Any column name except ROWID must be entered in lower-case letters.
After the table and column have been specified, the menu title is entered. This is automatically converted to upper-case letters. The colon will be supplied by xxx; if you supply one too, two colons will appear when the program is run.
Finally, the basename of the generated files is specified. This is restricted to seven lower-case characters because the xxx source files will be given an extra letter and `.4gl' as a suffix, which uses up to 12 characters, and 2 characters will be needed for the `s.' prefix supplied by SCCS, giving a total of 14 characters which is as long a name as System V Unix allows. All the generated files will start with the specified basename, but the standard files which are copied into the directory will have other names.
Frequently, this will be all the information you need to specify because the rest of the input is supplied with default values of `Y' that specify a fully-featured SPI.
However, you can elect to omit any of the sections listed below by changing the option value to `N':
If both the Add and Update code are omitted, the values specified for the before field, after field, control-P and control-B flags are irrelevant as no input code will be generated. It is not a good idea to omit either the before field or the after field code.
Once the ESCAPE key is hit with the data fully defined, FGLBLD will generate the code. It does this in 9 phases:
If no errors or warning were generated, the program can now be run.
If you accidentally (or deliberately) create a new file with the same name as some existing file, you will get one or two cryptic messages. If you have not previously offended like this, you will see a message such as:
mv filename.4gl o.filename.4gl
If you have already committed the same offence, you will get a message such as:
rm -f o.filename.4gl
mv filename.4gl o.filename.4gl
The pop-up option in the main menu leads to the sub-menu shown in Figure 4.
FIXME: fig 4
The Popup menu. popmenu.pic
The Conditional option is used to generate a conditional popup -- the type which allows the user to specify what they wish to choose from. The Unconditional option is used to generate an unconditional popup -- the type where the user gets no choice about what to choose from. the Database option allows you to change the current database. The Exit option returns you to the main menu.
Pop-ups let the user choose from a list of possible values.
To be effective, the list should not be so long that the user gets annoyed with having to scroll through the list to find the required value. An unconditional popup is all that will be needed when a user will only have to choose between a few possible values.
Unconditional pop-ups frequently need an argument which indicates which subset of a large number of possible values should be shown.
A conditional popup is needed when there are a very large number of possible choices and the user will need to be able to reduce the list to a more manageable length by specifying some criteria for which rows should be displayed.
In extreme cases, a full SPI could be used as a super-popup -- it gives the ultimate in flexibility -- but be careful of carrying this too far. One technical tour de force consisted of an SPI which used a second SPI as a popup; the second xxx itself used a third SPI as a popup; and the third SPI used a fourth SPI as a popup. The fourth SPI only used standard pop-ups. Although this worked -- and was very powerful and versatile in the hands of its designer -- it was beyond the capacity of the end users and was never used seriously.
A good illustration of when to use a conditional popup and when to use an unconditional one is in the code for FGLBLD:
the table name popup is conditional, and the column name popup is unconditional. In a database, there may be several hundred tables. The user normally knows roughly which tables are relevant and does not want to have to scroll through the complete list to find the required name. Additionally, there is a problem with writing the code: how big should the list be? And what happens when the actual number of tables in the database grows too large?
A general purpose program such as FGLBLD cannot just ignore this problem, and it cannot just forget to show the extra tables to the user because Sod's Law Sod's Law, also known as Murphy's Law, states:
`If anything can go wrong, it will'.
The first corollary is:
`Even if you thought it couldn't go wrong, Sod's Law still applies'.
dictates that the omitted tables will be the ones that FGLBLD's fussiest user will be having difficulty remembering. Thus, for choosing tables, a conditional popup is necessary.
One the other hand, once the table is chosen, there are normally only a few columns in the table and all the columns can be shown. In the unlikely event that some columns are omitted from the list, it shouldn't matter since the primary key column would normally be one of the first few columns in the table. By allocating a list of fixed size, and by passing the table name (or number) as an argument to the popup function, the use can choose from a list without having to specify a query.
The Conditional option is similar to, but simpler than, the SPI option. As with the SPI option, a form is displayed and has to be filled in. As before, the current database is used, and if no database has been specified yet, you will have to choose one via the popup window.
When the database has been selected, there are five mandatory pieces of information to be supplied:
The mechanisms and constraints on entering the table name, primary key, menu name and file name are almost identical to those in the SPI option; the only difference is that the file name may have up to 8 characters (rather than 7) because no letter is put between the name entered in the form and the `.4gl' suffix.
The last item is more difficult to explain. In the generated code, there are two places where a message is produced which refers to what the user is selecting. For example, when the chosen table contained a list of jobs, the two messages were:
MESSAGE "Use cursor keys to choose job: ESC to select"
MESSAGE "Some jobs not displayed"
Because FGLBLD is not psychic, the user has to specify what should be put into these messages in the fifth field, the one which is labeled `What is being selected'. The value entered should be the singular (e.g. `job'). If the name does not simply take an `s' to make the plural (which is used in the second message), the source code will have to be edited.
Once these details have been specified and the ESCAPE key has been hit, FGLBLD will generate the code. It does this in 3 phases;
The Unconditional option is almost identical to the Conditional one; the only difference is that no menu is generated, so no menu name is entered.
All the other comments apply as before.
The Fetch option is the simplest of the code generating options. As with all the other code generating options, you specify the table and primary key and the file name, but with a fetch function, that is all that is specified. The same popup facilities are available for choosing the table and column as in the SPI option. The Fetch option generates the code very quickly.
There is one issue which causes trouble, namely that you need to specify a real column as the primary key. If the primary key for the table is composite, you will need to specify one of those columns as the primary key and then modify the generated code to add enough extra arguments to define the primary key completely. This is more fiddly than difficult -- see the section on `Modifying a fetch function'.
The database option allows you to choose a new active database. It pops up a small window and shows you the current database. You can enter a new database name, or leave the entry blank to continue with the same database. The same option is available from the Pop-up menu, and the same code is used if one of the code generating options is chosen when no database is active.
The name of the current database is shown on the status line of the menu as soon as possible. If the code is called from a menu, it is displayed immediately; if the code is called from a form, it is only displayed after the all the data has been entered in the form.
The Name option allows you to change the name of the program being worked on. It is always set when you generate a new piece of code, and it can also be set via the menu option. It actually defines the basename of the program; the other options will extend this name as appropriate so that they operate on the correct files.
The name of the current program is shown on the status line of the menu as soon as possible. If the name is specified from a menu, it is displayed immediately; if the name is specified as part of the data in a form, it is only displayed after the all the data has been entered in the form.
The Build option runs xxx to build the application. It actually runs:
${MAKE:-make} -f program.mk ${FGLBLD_I4GL:-rds}
If the exit status from xxx is 0, the next option is Run; otherwise, the next option is Modify.
Note that FGLBLD does not normally create `.4ge' files, though the rules in the makefile can be modified to do so very easily.
The Run option effectively runs the command below:
case "$FGLBLD_I4GL" in
c4gl) program;;
*) ${FGLGO:-fglgo} program;;
esac
The Modify option leads to the sub-menu shown in Figure 5.
FIXME: Fig 5
The Modify menu. modmenu.pic
If the Source option is chosen, it leads to the sub-menu shown in Figure 6.
FIXME: fig 6
The Source menu srcmenu.pic
If the Form option is chosen, the extension `.4pr' is added to the program name and the editor specified by DBEDIT (default vi) is run to allow you to edit the file. Similarly, if the Makefile option is chosen, the extension `.mk' is added to the end of the program name and the editor is run.
The Build, Run and Name options in this menu are identical to the Build, Run and Name options in the main menu.
When the Source option is chosen, it leads to the Source File menu shown in Figure 6. If the Input option is chosen, the extension `i.4gl' is added to the program name and the editor specified by DBEDIT (default vi) is run to allow you to edit the file. Similarly, the Report, Cursor, Main and Globals options add the extensions `r.4gl', `c.4gl', `m.4gl', and `g.4gl' and then run the editor.
These options are used when the program name is the name specified when an SPI was generated. The Other option is used when the program name is the basename of a popup function or a fetch function or some other piece of xxx code; it adds the extension `.4gl' and runs the editor. The Exit option exits from the menu without editing anything. All the options edit a single file and then return to the Modify menu shown in Figure 5.
FGLBLD code uses a large number of environment variables. The ones which can be set by the user to affect its behavior are:
Rmk is available through Sphinx. It has a better understanding of than standard versions of . Except that it does not have the concept of `.KEEP_STATE:', it is as powerful as the version of under SunOS, plus it understands better than that version does (but only by a small margin).
This section gives you a guided tour around an application generated by FGLBLD. It discusses the various files which are generated and what is found in them, using an example database (one which was extensively used while testing FGLBLD).
The next main section (Section 1) describes how the code can be modified to improve the appearance for the user.
There are some conventions observed by all the code generated by FGLBLD which should be pointed out. These conform with the proposed internal standards at Sphinx, and have been used as de facto standards on some projects for a considerable time.
The database used for the example is called consult and contains three tables. The first table, Clients, briefly describes clients; they are the people who pay for work to be done.
"The CLIENTS table."
{
@(#)clients.sql 1.1 89/10/09
@(#)Create Clients Table
}CREATE TABLE Clients (
Clientid SERIAL NOT NULL,
Client CHAR(40) NOT NULL,
Contact CHAR(40) NOT NULL,
Phone CHAR(20) NOT NULL,
Telex CHAR(15),
Ansback CHAR(6),
Fax CHAR(15),
Notes CHAR(50));
{PRIMARY KEY Clients(Clientid) }
CREATE UNIQUE INDEX clients_1
ON clients(clientid);
The second table, Jobs, describes jobs done on behalf of clients; there may be several jobs for each client.
"The JOBS table."
{
@(#)jobs.sql 1.1 89/10/09
@(#)Create Jobs Table
}CREATE TABLE Jobs (
Jobid SERIAL NOT NULL,
Clientid INTEGER NOT NULL,
Started DATE NOT NULL,
Completed DATE,
Notesfile CHAR(50),
Notes CHAR(50));
{
PRIMARY KEY Jobs(Jobid)
FOREIGN KEY Jobs(Clientid)
REFERENCES Clients(Clientid)
}CREATE UNIQUE INDEX jobs_1
ON jobs(jobid); CREATE INDEX jobs_2 ON jobs(clientid);
The third table, Timesheet, records the time spent working on different jobs. This version is for a single-user database so there is no identification of the consultant doing the work.
"The TIMESHEET table."
{
@(#)times.sql 1.1 89/10/09
@(#)Create Timesheet Table
}CREATE TABLE Timesheet (
Ts_date DATE NOT NULL,
Ts_from CHAR(5) NOT NULL,
Ts_upto CHAR(5) NOT NULL,
Jobid INTEGER NOT NULL,
Notes CHAR(60));
{
PRIMARY KEY Timesheet(Ts_date, Ts_from)
FOREIGN KEY Timesheet(Jobid)
REFERENCES Jobs(Jobid)
}CREATE UNIQUE INDEX timesheet_1
ON timesheet(ts_date, jobid, ts_from); CREATE INDEX timesheet_2
ON timesheet(jobid);
As you can see from the descriptions, the Timesheet table cross-references the Jobs table, and the Jobs table cross-references the Clients table.
The generated SPI is based on the Timesheet table; it has a composite primary key so the ROWID was specified as the primary key when the code was generated. The menu title was `TIMESHEET', and the filename specified was `ts'.
This is essentially very simple. It defines a working record wr_timesheet which is used for all input and insert/delete/update operations, a null record nr_timesheet which is initialized to nulls, and a copy record cp_timesheet which contains the previous inserted or displayed value. The copy record is used when the program user presses control-P or F6. There is also a control record ct_timesheet which wraps most of the control information needed by FGLBLD into a single record.
"The globals file."
{
@(#)tsg.4gx 1.3 90/02/19
@(#)Built by: FGLBLD Version 6.10 (09/02/1990)
@(#)Global definitions for SPI on Timesheet
}DATABASE CONSULT
GLOBALS
DEFINE
wr_timesheet RECORD LIKE Timesheet.*, { Working record }
nr_timesheet RECORD LIKE Timesheet.*, { Null record }
cp_timesheet RECORD LIKE Timesheet.*, { Previous record }
ct_timesheet RECORDlist_number INTEGER, { List number }
active_set INTEGER, { Number of rows in active set }
active_row INTEGER, { Current row in active set }
direction INTEGER, { Moving forwards/backwards }
rep_query INTEGER, { Report enquiry constructed? }
rowid INTEGER, { Rowid is not part of wr_timesheet }
query_done SMALLINT { General query constructed? }END RECORD
END GLOBALS
This file will not be shown in full - it's big and boring. It seldom needs much changing. The cursor positioning code should not be changed without serious study.
The default main program is very simple:
"The main program."
{
@(#)tsm 1.3 90/02/19
@(#)Built by: FGLBLD Version 6.10 (09/02/1990)
@(#)Main control program for SPI on Timesheet
}DATABASE CONSULT
GLOBALS "tsg.4gl"
{ Module variables -- not accessible outside this file }
DEFINE sccs CHAR(1) { Identifier string }{ Dummy main program -- does the minimum reasonable work }
MAIN
LET sccs = "@(#)tsm 1.3 90/02/19"
CALL std_options("BASEDIRECTORY", "TS", "NONE")
DEFER INTERRUPT
DEFER QUIT{ Initialise the SPI for timesheet -- terminate on failure }
IF wop_timesheet() != 0 THEN
EXIT PROGRAM 1
END IF{ Can call mnu_timesheet many times }
CALL mnu_timesheet(){ Normally call wcl_timesheet just once }
CALL wcl_timesheet()END MAIN
The variable sccs is there to allow version control information to be embedded in the software. It is assumed that the generated code will be installed under xxx. Apart from setting options and the help file, it defers interrupt and quit signals, opens the window and initializes the SPI with wop_timesheet, calls the main menu function mnu_timesheet, and then closes the window and terminates the SPI with wcl_timesheet.
There is probably merit in the argument which says that the window handling should be done separately from the SPI initialization and termination. There should probably be a function whd_timesheet which would do all the window handling controlled by an argument -- it would be a case statement -- and there should be another function spi_timesheet which controls the initialization and termination of the SPI. This would not matter in the basic SPI, but would simplify the division of labor if several SPIs were to be handled by one program.
The mnu_timesheet function provides the main menu. This should not need much attention unless you wish to change an option name or if you modify the behavior of the Add option. Note the systematic structure of the options.
"The main menu (extracts)."
FUNCTION mnu_timesheet()
DEFINE
offset INTEGER, { Amount to jump by (next/previous) }
junk INTEGERCALL wmn_timesheet(2)
LET offset = 0
{ If re-entering this query screen }
IF ct_timesheet.active_set > 0 THEN
LET junk = csr_timesheet('C', offset)
END IFMENU "TIMESHEET"
COMMAND "Query" "Select set of data" HELP 1
CASE qry_timesheet('Q')
WHEN 0 NEXT OPTION "Query"
WHEN 1 NEXT OPTION "Next"
WHEN 2 NEXT OPTION "Previous"
END CASE
CALL check_interrupt()
LET offset = 0COMMAND "Next" "Show next row of data" HELP 2
LET ct_timesheet.direction = 1 { Forwards }
CASE csr_timesheet('N', offset)
WHEN 0 NEXT OPTION "Query"
WHEN 1 NEXT OPTION "Next"
WHEN 2 NEXT OPTION "Previous"
END CASE
CALL check_interrupt()
LET offset = 0...
COMMAND "Add" "Add new row of data" HELP 4
CASE ins_timesheet()
WHEN 0 NEXT OPTION "Query"
WHEN 1 NEXT OPTION "Next"
WHEN 2 NEXT OPTION "Previous"
END CASE
CALL check_interrupt()
LET offset = 0...
COMMAND "Exit" "Exit TIMESHEET Menu" HELP 8
LET INT_FLAG = FALSE
MESSAGE ""
EXIT MENUCOMMAND KEY('0') LET offset = 10 * offset + 0
COMMAND KEY('1') LET offset = 10 * offset + 1
...COMMAND KEY(F) { "First" "Jump to first selected row" }
CASE csr_timesheet('F', offset)
WHEN 0 NEXT OPTION "Query"
WHEN 1 NEXT OPTION "Next"
WHEN 2 NEXT OPTION "Previous"
END CASE
CALL check_interrupt()
LET offset = 0...
COMMAND KEY('!')
CALL shell_escape()
{ An interrupt may have terminated the shell }
LET INT_FLAG = FALSE
LET offset = 0COMMAND KEY(CONTROL-Y)
CALL do_screen_dump()
COMMAND KEY(F9)
CALL do_screen_dump()
END MENU
CALL wio_timesheet(3)
END FUNCTION {do_timesheet}
One pleasant feature of the FGLBLD SPI is that if you use `N' for Next once and subsequently use RETURN to continue stepping through the list, the list flashes up the message `No more rows going forwards' and changes the default option to Previous. If you continue to hit the RETURN key, you will step back to the start of the list, and the message `No more rows going backwards' will be displayed and the direction of travel will be reversed again.
There is also the facility to type `32N' to go forward 32 items in the list, or `32P' to go back. Overshooting the end of the list simply retrieves the last row. The `23G' facility jumps to the 23rd record in the list, if there are that many records. The `F' and `L' options go to the first and last rows respectively.
The del_timesheet function controls what happens when a row is deleted. It starts a transaction, fetches the current record for update and gets the user to confirm that the record should be deleted. The actual delete operation is done by the function iud_timesheet, which also handles insert and update operations. The D-List is updated by removing the current record and then csr_timesheet is called to sort out what should be displayed next.
Note that unlike the most recent versions of xxx, the user does not have to use either Next or Previous to see the next field after a delete; the next record in the direction in which the list was being traversed is automatically displayed after a record is deleted. The only time this doesn't happen is if the last record in the list id deleted and then the user is told that all the records have been deleted and is left with Query as the next option.
The upd_timesheet function controls what happens when a row is updated. It starts a transaction, fetches the current record for update, allows the user to modify the record (via the input code in inp_timesheet) and then calls iud_timesheet to handle the actual update. If necessary, the D-List record is updated, and then csr_timesheet is called to sort out what should be displayed next.
The ins_timesheet function controls what happens when the Add option is chosen. As generated, it starts a transaction and allows the user to add a sequence of records via inp_timesheet. As each record is inserted, it is left on the screen for a couple of seconds before clearing the screen to allow the next record to be added. There are three keys which terminate the input loop; the interrupt key, F8 and control-E.
When the loop is terminated, so is the transaction. The first added record is displayed via csr_timesheet. One standard modification is to remove the loop, which is simple enough. This also requires a modification to mnu_timesheet; the code there should make Add the next option.
The csr_timesheet function is rather complex because it is used to reposition the cursor in a large number of different ways. It also has to handle the problems caused by two users working on the table at the same time and both doing update and delete operations. It seldom needs modification except for the code which displays the number of the current record and the current size of the list.
This file is by far the messiest because it contains all the references to all the cursors used throughout the system. This means it contains initialization code, code to handle constructing the query and fetching the data, and also some of the code to handle reports. It contains the following functions:
wop_timesheet
wcl_timesheet
wcl_timesheet
qry_timesheet
new_timesheet
iud_timesheet
rnq_timesheet
rdf_timesheet
gtu_timesheet
edu_timesheet
get_timesheet
This code seldom needs much modification; the main changes would be in the ordering of the data returned by the main query. Since the main query only returns the primary key data by default, the SELECT statement would need to be augmented by the columns on which the data was to be sorted, and the retrieving code in new_timesheet and rdf_timesheet would need to be modified to handle the extra returned data. The SELECT statement which generates the data for an `All' report would also need to be modified.
Indeed, it can be argued that it should also go via cns_timesheet passing an argument to means that the CONSTRUCT statement should be bypassed and a null where clause inserted; this would mean that only one piece of code would need to be modified to ensure that the same ordering is used throughout the program.
This is normally the largest file, and it is the one which most frequently needs editing.
The file defines a number of module variables, including a record pr_timesheet which is used to preserve the value which was in the current field of the working record before the field was entered so that the new value can be compared afterwards if necessary, or the original value can be restored if the new value is rejected after validation.
There is also the record fc_timesheet which contains field control information. It is used particularly when the user uses F5 (for a popup) or F6 (for copying the previously displayed value into the current record) to ensure that if the user was going forwards through the form, the cursor continues forwards, and if the user was going backwards, the cursor continues moving backwards. This is a feature not supported by xxx any more.
There are a variable number of routines in this file. Assuming that there is any input code, the following routines are always present:
Additionally, there is a validation function for each column in the table with names v01_timesheet, v02_timesheet, v03_timesheet, etc.
As supplied, the function dis_timesheet is trivial and simply displays the working record to the form. It is normally modified to do whatever lookups are necessary (using fetch functions, of course) and then display the associated lookup data. This function is also called by csr_timesheet to display whatever row of data it finds, which ensures uniform display behaviour.
The hlp_timesheet function is called when the user hits control-F or F7 key and it displays a field-specific help message.
The function sdf_timesheet is called whenever a record is being added to set the default values for a record. This could make use of any control information available to the program, including the previous record which was added (which is available in the copy record cp_timesheet).
By default, it uses the INITIALIZE statement. (In versions of FGLBLD up to and including 6.05, this routine is far from optimal -- there should be a record which is initialized via INITIALIZE once, and this record should be copied into the working record; after that, specific initializations can be performed if necessary.)
The function spf_timesheet should never need modifying; it simply sets the previous field number in the field control record.
The input function inp_timesheet is extremely long and repetitious because xxx forces it to be like that if it is to handle all the input requirements sensibly.
"The input function (extracts)."
{
@(#)tsi.4gl 1.3 90/02/19
@(#)Built by: FGLBLD Version 6.10 (09/02/1990)
@(#)Input function for SPI on Timesheet
}DATABASE CONSULT
GLOBALS "tsg.4gl"
{ Module variables -- not accessible outside this file }
DEFINE
pr_timesheet RECORD LIKE Timesheet.*, { Previous contents of fields }
dr_timesheet RECORD LIKE Timesheet.*, { Default values for table }
fc_timesheet RECORD { Field control information }curr_field INTEGER, { Current field number }
prev_field INTEGER, { Previous field number }
n_iofields INTEGER { Number of I/O fields }END RECORD,
defset INTEGER, { 0 => default record not set }
iomode CHAR(1), { I for input, U for update }
sccs CHAR(1) { Identifier string }...
{ Input function }
FUNCTION inp_timesheet(iucode)DEFINE
instatus INTEGER,
field_no INTEGER,
iucode CHAR(1) { 'I' Insert, 'U' Update }LET instatus = TRUE { OK }
LET iomode = iucode
LET fc_timesheet.n_iofields = 5
IF iomode = 'I' THEN
CALL sdf_timesheet()
END IF
LET fc_timesheet.prev_field = 0CALL wi1_timesheet(2)
INPUT wr_timesheet.* WITHOUT DEFAULTS FROM s_timesheet.* HELP 20
ON KEY(F9, CONTROL-Y)
CALL do_screen_dump()
ON KEY (F8, CONTROL-E)
# Alternative exit input for FGLDB
LET instatus = FALSE
EXIT INPUTON KEY (F7, CONTROL-F)
CALL hlp_timesheet()
ON KEY (F6, CONTROL-P)
CASE
WHEN INFIELD(ts_date)
LET field_no = v01_timesheet("^P")
...
END CASE
GOTO nxf_timesheetON KEY (F5, CONTROL-B)
CASE
WHEN INFIELD(ts_date)
LET field_no = v01_timesheet("F5")
...
OTHERWISE
ERROR "No pop-up facility is defined for this field"
END CASE
GOTO nxf_timesheetBEFORE FIELD ts_date
LET field_no = v01_timesheet("BF")
GOTO nxf_timesheetAFTER FIELD ts_date
LET field_no = v01_timesheet("AF")
GOTO nxf_timesheet...
LABEL nxf_timesheet:
IF field_no IS NOT NULL THEN
CASE
WHEN field_no = 1
NEXT FIELD ts_date
WHEN field_no = 2
NEXT FIELD ts_from
WHEN field_no = 3
NEXT FIELD ts_upto
WHEN field_no = 4
NEXT FIELD jobid
WHEN field_no = 5
NEXT FIELD notes
END CASEEND IF
END INPUT
IF INT_FLAG = FALSE AND instatus = TRUE THEN
# You should modify this.
# AFTER INPUT type validation is often easier here than in an
# AFTER INPUT clause within the INPUT statement.ELSE
LET INT_FLAG = FALSE
LET instatus = FALSEEND IF
MESSAGE ""RETURN instatus
END FUNCTION {inp_timesheet}
The main feature of the code is that a single validation function is called for each field for each of the BEFORE FIELD and AFTER FIELD actions, and also for the popup (control-B/F5) and copy (control-P/F6) functionality.
This is crucial for the clean operation of the SPI. In too many hand-crafted programs, the validation code for the one field is split into three blocks, one for the AFTER FIELD clause, one for the ON KEY (F5, CONTROL-B) clause and one for the ON KEY (F6, CONTROL-P) clause -- if that is provided.
This is disastrous because when some aspect of the validation changes, one of the blocks is forgotten and the validation applied now depends on how the data was entered by the user.
By channeling all these bits through a single function (for each field), there is some chance that consistent validation will be applied however user enters the data. It is, of course, possible to subvert this intention, but on your own head be it!
Note that there is an abandon input key in the form of the ON KEY (F8, CONTROL-E) clause. This was originally provided to solve the problem that in fgldb, an interrupt transfers control back to the debugger and does not terminate the input statement; similarly a quit would only transfer control, not terminate the input. These keys provide a route to terminate the loop cleanly, and are sufficiently useful to be retained permanently.
The modifications made to this function are normally either to eliminate calls in the ON KEY (F5, CONTROL-B) block for those fields which will never be given a popup for the user to choose from, or to add code for the AFTER INPUT clause (which is not provided by default) or the extra checking spot after the INPUT statement (which is provided).
A standard, complete validation function fresh out of FGLBLD is shown below.
"The standard validation function."
# Validation Functions
# ********************
# Unless a non-null value is assigned to retval,
# the INPUT statement will continue in the default manner.
# Do not assign a non-null value to retval without cause.
# In general, do not set retval for BF.
#{ Validation code for Timesheet.ts_date }
FUNCTION v01_timesheet(vcode)DEFINE
# R_xref RECORD LIKE Xreftable.*,
vcode CHAR(2), { AF, BF, ^P or F5 }
retval INTEGER { Next field number }LET retval = NULL
CASE
WHEN vcode = "^P"
LET wr_timesheet.ts_date = cp_timesheet.ts_date
LET retval = next_field(fc_timesheet.*)
WHEN vcode = "F5"
ERROR "Sorry -- pop-up facility is not available"
LET retval = fc_timesheet.curr_field
# LET wr_timesheet.ts_date = pop_xreftable()
# LET retval = next_field(fc_timesheet.*)
WHEN vcode = "BF"
LET fc_timesheet.curr_field = 1
LET pr_timesheet.ts_date = wr_timesheet.ts_date
# Insert code to skip ts_date here
# WHEN vcode = "AF"
# Normally there is no code needed hereEND CASE
# Do not validate in BEFORE FIELD (normally)
IF vcode != "BF" THEN# CALL sel_xreftable(wr_timesheet.ts_date)
# RETURNING r_xref.*
# IF r_xref.ts_date IS NULL THEN
# DISPLAY "Unknown xref value ", wr_timesheet.ts_date
# LET wr_timesheet.ts_date = pr_timesheet.ts_date
# LET retval = fc_timesheet.curr_field
# END IF
CALL dis_timesheet()END IF
CALL spf_timesheet(vcode, retval)
RETURN retval
END FUNCTION {v01_timesheet}
The first thing to note is that there are a large number of hash comments, most of which can be deleted immediately. If the field is to acquire a popup, the skeletal popup code should be removed and the code which is hash-commented out should be enabled.
There should almost never be any code in the `AF' case. This implies that there is some validation which should be done here which should not be done in other circumstances -- something which is unlikely to be sensible.
If a field should have a popup, the code below can be regarded as a sort of template for the validation process.
"A validation function with pop-ups."
{ Validation code for Timesheet.jobid }
FUNCTION v04_timesheet(vcode)DEFINE
r_jobs RECORD LIKE Jobs.*,
vcode CHAR(2), { AF, BF, ^P or F5 }
retval INTEGER { Next field number }LET retval = NULL
CASE
WHEN vcode = "^P"
LET wr_timesheet.jobid = cp_timesheet.jobid
LET retval = next_field(fc_timesheet.*)
WHEN vcode = "F5"
LET wr_timesheet.jobid = pop_jobs()
LET retval = next_field(fc_timesheet.*)
WHEN vcode = "BF"
LET fc_timesheet.curr_field = 4
LET pr_timesheet.jobid = wr_timesheet.jobidEND CASE
IF vcode != "BF" THEN
CALL sel_jobs(wr_timesheet.jobid) RETURNING r_jobs.*
IF r_jobs.jobid IS NULL THEN
DISPLAY "Unknown job value ", wr_timesheet.jobid
LET wr_timesheet.jobid = pr_timesheet.jobid
LET retval = fc_timesheet.curr_field
END IF
CALL dis_timesheet()
END IFCALL spf_timesheet(vcode, retval)
RETURN retval
END FUNCTION {v04_timesheet}
The report file tsr.4gl contains two functions and a report; it should contain just a report.
The two functions are rch_timesheet which offers the report menu and rln_timesheet which processes one line of data. These two functions should go in the cursors file. You may want to modify the menu which is offered by rch_timesheet; the one that is offered includes all the options which might be relevant as visible options, and it might be sensible to hide some of the options.
Note that the menu is too long to fit on one line so it is split and the All and Exit options are not immediately visible. If the report needs some information looked up as well as the main record, this can be done in either rln_timesheet or the report itself. You should (or, at any rate, could) use fetch functions for these lookups.
The report itself is only a marginal improvement on a default report. Consequently, it will need to be extended and written properly. Originally, as the code suggests, there was a hook in the report which could dynamically configure the dimensions of a report. This facility still works in compiled xxx but is not available in Informix-RDS (because the interpreters fglgo and fgldb do not compile), so it is omitted from the standard distribution.
The form file needs the normal tidying up that any other generated form does. This means translating the column names into sensible labels, modifying the layout so that things align better and so on. It also means adding sensible attributes, especially COMMENTS for those fields with a popup so the user is reminded that there are pop-ups.
Note that the form provided by FGLBLD has a blank line before the end of the screen; this is necessary to avoid having comments and messages overwriting the data on the bottom line of the form.
Do not reorder the fields on the form or in the attribute section without consulting the section on modifying the order of the fields - this is a very tricky subject to deal with.
By all means add lookup fields to help the user understand the coding on the form. One convention for these fields is to define the screen record s_display which is where the relevant values are displayed by dis_timesheet.
The makefile ts.mk can be used for either compiled xxx or for Informix-RDS. When you add popups and fetch functions, you should normally append the xxx file names to the list FILES.o with the extension `.o' in place of `.4gl', and the form files to the list FILES.frm with the extension `.frm in place of `.4pr'.
If you want to put the object files into a library, you will need to handle the library rules instead. If you ever create code which uses the globals file, do not forget to add the dependency lines of the form shown by:
tsc.4go tsc.o: tsg.4gl
However, before you add such a rule, ask yourself `why does this code use the globals file at all?' There are reasons for doing so, but it is seldom necessary.
The help message file ts.msg has a default message for each field in the INPUT statement. You should provide a sensible message in its place. You may want to modify some of the other messages; the language is a bit stilted in some places because it has to be applicable to any possible table.
The other files provided by FGLBLD should not need changing. The files repdest.4gl and repdest.4pr implement the function report_destination; you may wish to modify this. If you are using compiled xxx and have got hold of the code to dynamically configure report dimensions, you will want to use this to change the layout of a report to screen to 23 lines, with no (zero) top margin, bottom margin and left margin.
The file informix.mk contains the rules that xxx needs to understand how to compile xxx files of most sorts. If your version of xxx does not understand the rules, the chances are you have a very old version and you would do better to get hold of rmk. If your version of xx understands lines of the form:
include ${BASEDIRECTORY}/etc/informix.mk
then it is recommended that you put a copy of informix.mk in one central directory (e.g. the project's etc directory) and use lines like the one above in the makefiles.
If you think of modifying the rules to handle functions such as ensuring that when a program is compiled, a link in a remote directory is kept up to date, then if your version of xxx does not support the construct above, get hold of a version that does.
There are an infinite number of changes that can be made to the source code, and some of the changes have been outlined in the previous section. This section goes into more detail about what should be done to the source and covers various situations in more detail.
Although this section is based on the experience of people who have used FGLBLD for some fairly extensive project work, there is a distinct possibility that you will discover a better solution to some of the problems. If you do find such a solution, please let Sphinx know.
The INPUT code
Changing the order of the fields
Splitting a large table
Modifying the report
Modifying a popup function
Modifying a fetch function
There are two parts of FGLBLD that use the support code. First of all, some code is needed with each generated program; this means the D-List code, the xxx rules informix.mk, and the files perfaux.4gl, repdest.4gl, repdest.4pr and popstr.c.
Second, there is the code needed by the custom version of fglgo and fgldb. At first sight, this should be the same as the code needed by the generated code; however, if FGLBLD is compiled using Informix-RDS, it has to be run with a custom runner which has a number of extra functions in it, and these are also supplied to the user.
The D-List code consists of 6 source files and 5 manual pages. The source files are:
The interesting code is in dlist.c; this actually handles the D-List proper. The interface code in the other `.c' files is maintained by parallel editing -- a little hard work would probably allow one file to be used for all 4 interfaces.
For each of the `.c' files, there is a corresponding `.man' file which can be printed using xxx or xxx with the `-man' option. For more information, look at the manual pages or the source code.
The file informix.mk is used to tell Augmented Make (which is the version normally available on System V Unix) how to compile xxx programs. The rules are included by the include directive in the makefile generated by FGLBLD. Only one copy is needed per directory; it is not altered when FGLBLD creates it.
There is one oddity already mentioned, namely that the forms which are generated by FGLBLD have the extension `.4pr' instead of `.per' as would normally be used. This allows you to distinguish between form files for use with xxx and ones for use with (xxx). The rules in informix.mk handle both extensions and give `.4pr' files precedence over `.per' files.
The rules do not cover converting `.4gl' files into `.ec' or `.c' files -- there is not normally any need to do this, and if there is, you can probably manage the necessary changes yourself. Similarly, there is no rule for converting `.ec' files into `.c' files.
If the database has a transaction log, all operations using update cursors must occur inside a transaction. Further, when the transaction is committed (or rolled back), all active (open) cursors are closed. The seemingly anti-social behavior makes life difficult for programmers, but simplifies the transaction handling code inside the database agent and also corresponds to the ANSI standard definition of how COMMIT WORK should behave.
It also means that the program must be careful about how it handles transactions; it is not acceptable to put BEGIN WORK immediately in front of the main menu and COMMIT WORK after it for several reasons.
First, each row that is selected by an UPDATE CURSOR is locked until either the next row is fetched (if the row was not changed) or until the next COMMIT WORK (if the row was changed). A user could edit a lot of rows of data, and each one would be locked against other users until the COMMIT WORK. This reduces the usability of the system. More seriously, there is normally a limit to the number of locks the system can maintain, so a protracted transaction might fail simply because the user updated too many rows. If the transaction did fail, the user have to redo all the changes they had made, and would be mightily displeased.
At the other extreme, each update and delete operation could be a separate transaction, and each sequence of inserts another. This uses the minimum number of locks, and because the FGLBLD code uses D-Lists, it gives an entirely acceptable performance for the user.
If SCROLL CURSORS were being used in place of D-Lists, it would imply that the SCROLL CURSOR will be re-opened every time a change was made: this would be acceptable if the user was primarily browsing through the data but not if they were performing a major series of updates.
Transaction logs can be made to come and go -- it isn't always easy, but it can be done. Ideally, the generated programs should continue to work regardless of whether there is a transaction log or not. To achieve this independence, a set of functions should be used to BEGIN, COMMIT and ROLLBACK WORK, not the raw commands. These functions are used where the analogous construct should be invoked, but wrap the code in a conditional test which checks whether there is a log or not. The functions are:
There is an additional function end_work which takes an argument: if the argument is zero, it does COMMIT WORK, otherwise it does ROLLBACK WORK:
CALL end_work(opstatus)
To find out whether there is a transaction log present, there is a function called translog which returns 1 (true) if there is a transaction log present and 0 (false) if not. This routine only tests the database once, so it is not an expensive operation, but the other routines are the only ones that need to use it normally.
IF translog () THEN
MESSAGE "Transaction Logging is enabled"
END IF
By using these routines, a program can be created which will work correctly whether or not the database has a transaction log. These transaction handling routines are part of the code in the file perfaux.4gl which is produced every time an SPI is generated.
The code generated by FGLBLD uses these routines and can be run on a database which has a transaction log as well as a database without any transaction log, and the transaction log can be added or removed after the program is compiled and the program continues to work correctly.
There are two other routines in perfaux.4gl; these are shell_escape which is a primitive but effective way of letting the user get access to the operating system, and check_interrupt which is called inside the main menu generated by FGLBLD to clear the interrupt flag and produce a suitable warning message.
The shell escape routine can usefully have several extra features added; first it can loop to allow someone to enter several commands in a row, and secondly, security control could be added so that only privileged users could do shell escapes at all. This second change would need to be part of an integrated security control package.
In code developed by Sphinx, the startup code in the MAIN program would call a function (normally called std_options) with a set of arguments to identify the program, the help message file, the error log file and any necessary security clearance. This function would also set the options in a project-standard way. This enforces a uniform approach to options.
Note that DEFER INTERRUPT and DEFER QUIT must be placed in the MAIN program.
The shell script mkfglgo provided with an Informix-RDS version of FGLBLD copies the necessary source files from the FGLBLD private directory top the current directory and then compiles fgldb and fglgo, in that order, using the makefile i4glrds.mk, which is also provided. If you wish to add extra functions to your custom versions of fglgo and fgldb, you will need to edit the makefile and also fgiusr.c. When you add the functions to the list inside fgiusr.c, prepare the external declaration in the same format as the one for round (for example). Then jump to the bottom of the file and (using vi) do what it says in the comment.
There is a complete set of documentation for the various facilities which are included in these versions of fglgo and fgldb: take a good look - there may be some pleasant surprises in store for you.
The FGLBLD installation process is closely modeled on the system described in the document `Distributing Software', or maybe the process described there is closely modeled on the installation process developed for FGLBLD -- the relationship is close in either case.
FGLBLD source code, as well as pre-compiled binaries are available from Aubit project. Source code is also available from Aubit project CVS.
You will have received a set of source files which should be copied off the distribution media into one directory. You will need about 3 megabytes of free disc space on the file system where the software is to be compiled, and an extra megabyte on the file system where the software is to be installed unless you do some backup and cleanup work prior to installing the software.
There is some minimal configuration work to be done. There are two variants of the case-insensitive match option to grep; some versions use `-i' and others use `-y'. The `.alt' scripts are distributed to use the `-y' option and may need to be modified to suit your system.
To compile the software, type the relevant one of the commands:
Stand well back and wait. When it is complete, there will be a sub-directory called Distribution which will contain all the material to be copied onto the distribution media. The distribution media should be made by changing into the Distribution directory and then archiving everything in that directory and below using cpio or tar as required.
If the software is only to be installed on the local machine, it can be installed by running the installation process in the Distribution directory -- see the section on `Installing FGLBLD'.
If there are any problems, the most likely problems are:
A binary distribution of FGLBLD can be copied off the distribution media onto any convenient portion of the disc as the installation process will relocate the software if necessary.
To install the software, you will need about 1 megabyte of spare disc space.
The actual installation process is done by root, though as no files need special privileges, it could be installed by some other user provided the install script was suitably hacked -- all it requires is one line added after the line which starts
`uid=';
uid=0
The installation is performed by running ./install in the directory into which the software was copied. All the configuration parameters can be modified by setting environment variables. The required parameters are:
The distribution type is normally fixed by the supplier of the software but you may have a choice. The public directory should normally be something like $INFORMIXDIR/bin or /usr/local/bin which will already be on user's search paths, but it can be specified as $FGLBLDDIR/bin, in which case the designation `public directory' becomes a misnomer.
NB: it is assumed that you have the necessary version of xxx installed on the system. If you are using Informix-RDS, you should have a full development system and you will need a suitable C compiler available.
NB: if the public directory is already on people's search paths, there is no need for people to modify their environment to be able to use the FGLBLD. Otherwise, people will need to ensure that the public directory is added to their search path.
If required, the template files and code generator scripts can be modified to conform to local (or project) standards. The code generators are kept in $FGLBLDDIR/bin and have the extension `.alt'.
The template files are all kept in $FGLBLDDIR/src. Note that fglbld.alt generates all the code in the input and report files `programi.4gl' and `programr.4gl' without using a template, and that all the modifications made to generated forms are made within the `.alt' scripts.
Tip: if you are not creating a second installation of FGLBLD for a project on a machine where a standard distribution of FGLBLD will also be available, then create a directory called $FGLBLDDIR/Originals and copy all the `.alt' files and all the files in FGLBLDDIR/src into that directory before modifying anything. The Originals directory and all the files in it should not have write permission for anyone.
Many moons ago, FGLBLD ran with SCROLL CURSORS. Only a few routines in the `programc.4gl' file would have to be changed to use SCROLL CURSORS again, but there would be unpleasant consequences for the performance. Either the currency (up-to-date-ness) of the lists must be sacrificed to provide good performance, or performance must be sacrificed to improve the currency of the lists.
FGLBLD uses the `.4pr' extension for xxx form source files. An alternative extension `.4gf' was rejected as a Sphinx internal standard because it might subsequently be used by xxx.
The current version of FGLBLD uses xxx report code to generate a shell script that runs the code generator shell script rather than have all the skeletal code embedded within the xxx program. This has many advantages, not least of which are that the xxx is simpler and it is infinitely easier to modify the shell scripts than it is to modify xxx code, even with Informix-RDS. The code basically uses cp and sed and echo to generate the code, and fortunately, all modern shells have echo as a built-in command so the overhead of process forking and execing is reduced.
There are features missing from both FGLBLD and xxx which would make life easier for the user of FGLBLD.
A master/detail relationship exists between two tables when an entry in the master table may have associated with it zero or more entries in the detail table. The classic example of this is a sales order; the master table will have the information about which customer is doing the buying, the address to ship the goods to, order date, purchase order number, etc, and the detail table will list the quantity, part number, discount, etc for each line of the order
xxx provides the Master and Detail options. Once the master values have been specified, the user chooses the Detail option and xxx automatically selects all the rows that match the master values.
The user can then edit these in the same way as the master values -- notably they can add, update and delete detail rows. However, the add process is uncomfortable since it is necessary to use the add option, enter the data and hit the ESCAPE key for each row of data. When all the detail data has been entered, the user has to choose the Master option again, move on to (or add) the next set of Master data, and then use the Detail option again.
Using xxx, this process can be simplified because the user can be allowed to enter the master data, and when that is done, the program can give the user an INPUT ARRAY to enter the Detail data (no questions asked, no fiddle-faddle, just enter the data).
It does COMMIT WORK, for the second time, the program can insert all the data into the database, and the user can add the next master information. The update process should be a little different: it should generally show as much of detail data as there is room for and then offer a menu which allows the user to update either the master or the detail data. The Exit option of this sub-menu should have yet another sub-menu with options Save-and-Exit, Discard-and-Exit and Resume-Editing, with the obvious meanings.
The same input routines should be used in both the Update and Add options.
The Delete option will delete the master record and all the corresponding detail records. In general, the Query, Next and Previous option will only deal with the master record; there should usually be a Show option with fills in all the details on request.
This scheme may seem too elaborate but it has two advantages; first, the user can feel that they are in control, and second, it can be extended easily to cope with complex situations where one table is the master of several detail tables. It does not handle a three tier master/detail/sub-detail relationship, but this would need to be handled very carefully in any case to avoid confusing the user.
Throughout this description, terms such as should and could have been used to indicate that the method outlined here can be modified. For example, with some master/detail relationships, a repeating Add would not be desirable. The relationships discussed have been very closely bound together, and each master record has been master of but a few detail records (say 1-40). If the relationship is not as close, or if many detail records may apply to one master, it may be better to implement a full simplified xxx interface for the details, so that the user can edit the details in the same general way as in xxx.
The report facility provided by FGLBLD is crude; a better report generator facility would allow for a screen-based report using a form to display one page of information at a time, and would probably also provide more powerful facilities for controlling the layout of the reports for printers.
InFourGen-Report is supplied as a separate product from InFourGen-Screen; improved report facilities in FGLBLD would probably be provided as a separate, compatible product, too.
For a fuller list of features which xxx is lacking, see the document `Possible Improvements to Informix-4GL'. The ones which would make most difference to FGLBLD-generated code are:
The way in which the version number of FGLBLD is incorporated into the product will be modified.
If it seems necessary for the master/detail system, FGLBLD will acquire the ability to parse an existing form and generate code to handle that form. This would imply that reordering the fields would be done by the user before any code was generated, and that FGLBLD would handle the reordered input automatically.
More detailed information may be provided so that the generated code can automatically generate the code for the columns which need popup functions, and possibly even do some of the validation handling. This would need code to handle an INPUT ARRAY pre-initialized with the complete list of the table's columns.
It would be possible to generate fetch functions with a larger cache than the one record cache currently in use.
A cleverer report facility may be provided.
There may be the ability to specify the order in which the retrieved data is fetched by the SPI. At the moment, the data is in ascending order of the primary key column. This is often a sensible choice, but not when the primary key has to ROWID. The extra flexibility should be available.
At some stage, the process which specifies the SPI may have an INPUT ARRAY facility which would allow the detailed validation required for each column to be specified, including whether there should be a popup on the column (and if so, should it be generated, and what table does it cross-refer), and whether there is a lookup needed (if there is a popup, there should be a lookup, and the converse is often true), and so on.
Jonathan Leffler
6th October 1989
HTML version created by Andrej Falout
16/07/2002
FGLGEN source code is available under GNU GPL license. Code created by FGLGEN is not subject to this license, and user is free to apply any license to it he chooses.