4. Common Questions about tools:


4.1 Informix-4GL

4.1.1 I keep getting -4518 errors! How can I fix this problem?

The -4518 error is usually caused by running out of a resource known as temporary string space. Solutions include:

This error can be caused by any of the following coding structures:

LET l_str = "Hello ",myfunc("World"))
CASE statements without an OTHERWISE # Pre-4.1
CASE statements that look like this: # Pre-4.11 (maybe 4.10)
   CASE dummy_var 
        WHEN 0 
     ..... 
        WHEN 1 
     ..... 
        WHEN 2 
     ..... 
    END CASE 
       instead of this: 
    CASE 
        WHEN dummy_var = 0 
     ..... 
        WHEN dummy_var = 1 
     ..... 
        WHEN dummy_var = 2 
     ..... 
    END CASE 
Reports with excessive string usage (ie. USING, or other literals). One thing you can do to reduce the contents of string storage is to assign redundant strings associated with USING to variables and then refer to the variable:

let l_str1 = '$$$$$$$$.$$'

PRINT l_number USING l_str1

RETURNing large strings from functions.
Use of SPACE, SPACES, COLUMN, UPSHIFT, DOWNSHIFT, or WORDWRAP
Assignment of NULL to a string subscript:
      DEFINE x, y CHAR(10) 
      LET y = NULL 
      LET x[3,5] = y 
A return statement in a report function.
Passing an argument to a function which has no parameters defined:
   CALL something(my_variable)
...
   FUNCTION something()

From: heiker@quietsch.ping.de (Christian Heiker)

There is an Informix-Lib *(undocumented function)* called "acdealloc()" which job it is to clear the stack.

From a 4gl-program you can call this function with "CALL acdealloc()". With RDS you have to hook this function into your fguser.c module.

Here are the steps do do it with RDS:

edit file "fgiusr.c" (copy $INFORMIXDIR/etc/fgiusr.c)
int   acdealloc();
#include "fgicfunc.h"

cfunc_t usrcfuncs[] =
    {
    "acdealloc", acdealloc, 0,
    0, 0, 0
    };
create a new runner (fglgo)
cfglgo fgiusr.c -o newrun
Call the c-function in the RDS-program
CALL acdealloc()
Start the program with the new runner

You should pay attention that you make the call in an upper hierachy, for example after a FINISH REPORT.

There is no guarantee that other important data may be deleted from the stack (because nobody nows what is at which time on the stack).

BEWARE: There seems to be a bug in 4GL/ID (4.1 at least) which causes TSS problems to occur after the interrupt key has been pressed, which isn't very helpful if you're trying to find TSS problems in your program!!!

4.1.2 How can I write to a flat ASCII file?

Generate a REPORT with 0 TOP, LEFT, RIGHT & BOTTOM MARGINs and 1 line PAGE LENGTH.

4.1.3 How can I read from a flat ASCII file?

If you're talking about loading data which has been exported from another system you should just use the LOAD command.

If you want to do something sexier like suck in the output from a Unix command use either the routine in Appendix B (100% 4GL code) or Appendix C (a 'C' routine).

4.1.4 How can I force INPUT ARRAY onto the next line?

From: dennisp@informix.com (Dennis Pimple)

Here's a short article that addresses this and other INPUT ARRAY issues:

Emulating "NEXT ROW" syntax in INPUT ARRAY until version 5.0.

Create a screen record in which the last field in a NOENTRY field. This field can be one that actually stores data for display or a CHAR(1) designed just for this purpose. Remember that the last field in the screen record doesn't have to be the right-most field on the screen, but it usually is
SCREEN 
{ 
[a  ][b   ][c  ][n] 
... 
} 
 
ATTRIBUTES 
a = ... 
b = ... 
c = ... 
n = FORMONLY.ne TYPE CHAR, NOENTRY; 
 
INSTRUCTIONS 
... 
SCREEN RECORD s_array[##] (...,FORMONLY.ne) 
 
END
Define your array with the last field that matches the NOENTRY in the screen record:
DEFINE ma_array ARRAY[##] OF RECORD 
        ... 
        ne      CHAR(1) 
END RECORD 
... 
In your INPUT ARRAY function, you need counters for arr_curr, scr_line, arr_count, and one to mark if we want to jump rows:
DEFINE arr  SMALLINT 
DEFINE scr  SMALLINT 
DEFINE act  SMALLINT 
DEFINE arrg SMALLINT 
We surround the entire INPUT ARRAY with a WHILE loop, because the trick to moving backward is to mark the row to go to, then exit the input and loop back around to begin input again.
 
LET arrg = 1 
LET act = number of rows filled in the array already 
WHILE arrg > 0 
    CALL set_count(act) 
    INPUT ARRAY ma_array WITHOUT DEFAULTS FROM s_array.* 
        BEFORE ROW 
            LET arr = arr_curr() 
            LET scr = scr_line() 
            IF arrg > arr THEN 
                # this means we want to go to a row below us in the 
                # array, so NEXT FIELD the NOENTRY field, which means 
                # jump to the next row 
                NEXT FIELD ne 
            END IF 
            # reset arrg to 0 if we get to this point, which allows 
            # us to leave the WHILE loop around the INPUT ARRAY if 
            # the user hits the ACCEPT or INTERRUPT key 
            LET arrg = 0 
        ... 
        AFTER ROW 
            # I have found that AFTER ROW is the best place to track 
            # the last filled in row. 
            LET act = arr_count() 
        ... 
        # below is an example of how we can implement a function key 
        # to act as a HOME key. When the user types the function key 
        # designated as HOME, the cursor will move to array row 1 
        ON KEY (F##) 
            LET arrg = 1 
            EXIT INPUT 
        ... 
        # below is an example of how we can implement a function key 
        # to act as an END key. When the user types the function key 
        # designated as END, the cursor will move to the last filled 
        # in row. 
        ON KEY (F##) 
            IF arr .lt. act THEN 
                # if we're not on the last row now, go there 
                LET arrg = act 
                NEXT FIELD ne 
            END IF 
        ... 
    END INPUT 
    # in case we're looping we need to track arr_count here 
    # because an EXIT INPUT call avoids the AFTER ROW block 
    LET act = arr_count() 
END WHILE 
 
IF int_flag THEN 
... 

The only drawback to this approach is that for very large arrays the looping can take some time. But for arrays that are 50 rows or less, this is suitable.

BUG #11364 WORK AROUND

The only problem, now, is that with version 4.10 there is a bug that causes exiting from an AUTONEXT field into the NOENTRY field (and thus to the next row) to put the cursor into an infinite loop. This bug (#11364) is scheduled for a fix in version 4.10.UE1, which is currently shipping on some platforms. If you can't get the fixed version, there are two approaches to fixing the problem.

The first approach is to remove the AUTONEXT from the field just before the NOENTRY field. This is the simplest, and thus recommended, approach. If your user can't live with hitting the RETURN key after this field, though, you can use some of the new 4.10 library functions as a work around:

 
... 
AFTER FIELD before_ne 
    IF fgl_lastkey() = fgl_keyval("autonext") THEN 
        IF arr < maximum array size THEN 
            # go to the next row, avoiding bug 11364 
            LET arrg = arr + 1 
        ELSE 
            # we're on the last row already, loop back to the top 
            LET arrg = 1 
        END IF 
        EXIT INPUT 
    END IF 
... 

"before_ne" in the example above is the name of the AUTONEXT field that is listed just before the NOENTRY field in the screen record, and "maximum array size" is the size of array ma_array.

This work around has the same problem as the jumping solution in that for very large arrays a lot of screen repainting is done to get back down to the desired row.

4.1.5 How can I make a screen dump?

There is an undocumented environment variable called DBSCREENOUT which you need to set to the name of the file you wish you to use for your screendumps.

eg: DBSCREENOUT=screen.out

Now press CONTROL-P when the screen you want is displayed and it will be appended to your output file (screen.out in the above example).

NOTE: There is a similar environment variable called DBSCREENDUMP which includes some ESCape sequencing, so isn't as readable.

4.1.6 Any undocumented features that I could play with?

4.1.6.1 fgl_drawbox(depth, width, y, x, color)

A 4GL function which, surprize, surprize, draws boxes. It has now been documented in one of the 4GL for Windows manuals, but does exist on other platforms too.

The optional "color" parameter uses an integer code where 0=white, 7=black, and miscellaneous other colors in between.

4.1.6.2 FREE <statement-id|cursor-name>

This command wasn't made "official" until 4.0, but it does exist in earlier releases. It frees memory used by a cursor, and should be used only once you've completely finished with a cursor (ie: after a CLOSE)

statement-id is the name of a statement that has been PREPAREd

cursor-name is the name of a cursor whose DECLARE statement includes the keywords SELECT or INSERT

In 6.00, you can free a statement-id after you have declared the cursor. Also, in 6.00 you should free both the statement-id and the cursor.

4.1.6.3 Undocumented math functions from SELECT

        SELECT  ACOS(col1), 
                ASIN(col1), 
                ATAN(col1), 
                ATAN2(col1,col2), 
                COS(col1), 
                HEX(col1), 
                LOG10(col1), 
                LOGN(col1), 
                ROUND(col1), 
                SIN(col1), 
                SQRT(col1), 
                TAN(col1), 
                TRUNC(col1)       FROM foo_table; 

4.1.6.4 Is there a "get_key" function, or similar

In 4GL 4.1 and above you can use the fgl_lastkey function, but June Tong has a solution for pre-4.1 users:

Here is some information about the undocumented/unsupported/ replaced-by-fgl_lastkey() feature called eflastkey:

There is a global variable called eflastkey which is set in effin.c which holds the value of the last key typed. eflastkey is a short integer which will contain the ascii value of 'regular' keys typed and special values for keys for which 4GL does a 'mapping'. The ACCEPT KEY, for example is defined as 2016, LEFT arrow is defined as 2002 and RIGHT arrow is defined as 2003. If your linker accepts multiple global definitions of the same variable (treating one as the primary declaration and all others as extern) then you can simply include the variable eflastkey in the globals section of your 4GL program:

        GLOBALS
                DEFINE  eflastkey       SMALLINT
        END GLOBALS

        Then, during your INPUT statement, you can test the value:

        CASE eflastkey
        WHEN 2000               # UP ARROW
        WHEN 2001               # DOWN ARROW
        WHEN 2002               # LEFT ARROW
        WHEN 2003               # RIGHT ARROW
        OTHERWISE               # Some other key
        END CASE

If your linker gives you a problem with the definition of eflastkey, you can always call a C function such as CALL lastkey from your INPUT statement as:

lastkey()
{
        extern short eflastkey;

        switch (eflastkey)
        {
        case 2000:
                ...
        case 2001:
                ...
        case 2002:
                ...
        case 2003:
                ...
        default:
                ...
        }
}

You can test for other special mapped values such as INTERRUPT KEY which will set eflastkey to 2011 (assuming it has been DEFERed of course).

4.1.7 Performance tips (Code Optimization)

4.1.7.1 INITIALIZE vs LET

LET is more efficient than INITIALIZE, so if you're going to be setting a record to NULL a lot then you should probably INITIALIZE a NULL record once, and use LET for subsequent assignments.

 
   DEFINE p_customer RECORD LIKE customer.*   # working record 
          n_customer RECORD LIKE customer.*   # null record 
 
   INITIALIZE n_customer.* TO NULL 
 
             [later...] 
 
   LET p_customer.* = n_customer.* 

The overhead associated with INITIALIZE is signigicant, but only if you are setting records to NULL a lot.

4.1.7.2 String Constants

Use of in-line strings can cause a program to run out of temporary string space (in pre-4.12 versions of 4GL), so assign things like format strings and error messages to a common string variable and use that instead.

 
   DEFINE n10d2 char(10)    # Numeric, 10 wide, 2dp.   #######.## 
 
   DISPLAY l_val USING n10d2 

4.1.7.3 Displaying of forms

When a program has multiple screens, instead of opening each page as a separate form open them as full-screen windows.

 
   Better:                                Worse: 
   OPEN WINDOW a AT 1,1 WITH FORM "a"     OPEN FORM a FROM "a" 
   OPEN WINDOW b AT 1,1 WITH FORM "b"     OPEN FORM b FROM "b" 
   CURRENT WINDOW IS a                    DISPLAY FORM a 
   ...                                    ... 
   CURRENT WINDOW IS b                    DISPLAY FORM b 

Note: If you choose to use ATTRIBUTE(BORDER), which takes up a character position all around the form then you cannot open the form closer to 'home' than 2,2.

Kerry wants to know why opening screen-sized windows is better than just displaying a form. Answers on a postcard to: kerry@kcbbs.gen.nz

4.1.7.4 Function call vs RETURNING

When calling a function that returns a single value the following options are available...

Worst
CALL lookup() RETURNING statcode
Better
LET statcode = lookup()
Best
IF (lookup() = NOTFOUND)

4.1.7.5 ORDER EXTERNAL BY

When a report needs to be sorted use the "order external by" clause if the data comes into the report in the correct order, otherwise the data will be needlessly re-sorted (and disk-space will be needlessly gobbled up).

4.1.8 Performance tips (Design Optimization)

4.1.8.1 Modularize Executables

Keep executables small. Consider breaking the program into its component parts and RUN them from within a driver program.

eg: Invoice printing could be a standalone program RUN from order entry.

4.1.8.2 RUN WITHOUT WAITING

If RUNing another program use the "without waiting" clause to speed execution of the program which RUNs another.

eg: Order entry not having to wait for invoice to print before being able to key new order.

4.1.8.3 UPDATE WHERE CURRENT OF

When updating data from a scrolling list declare a cursor for update and then update the table using the "where current of" clause. ie:

 
   DECLARE c1 CURSOR FOR 
      SELECT * FROM customer FOR UPDATE 
   ... 
   UPDATE customer SET ... WHERE CURRENT OF c1 

And ignore the ghastly estimated costs from SET EXPLAIN ON; they are wrong.

4.1.8.4 INSERT CURSOR

When inserting many rows into the same table (eg: order lines) try using the insert cursor command to improve performance.

 
   PREPARE is1 FROM "INSERT INTO olines VALUES (ohintref, ohline, 
                     ohprodno ...)" 
   DECLARE ic1 CURSOR FOR is1 
   OPEN ic1 
   PUT ic1 FROM p_orderno, p_lineno, p_prodno... 
   ... 
   FLUSH ic1        # pump everything out of the buffer 

If precise error handling is more important than performance, use standalone INSERT statements (preferably EXECUTE an INSERT which has been PREPAREd). Error handling with INSERT cursors is difficult.

4.1.8.5 OPEN vs OPEN, CLOSE

Conventional Informix wisdom states that CLOSEing a cursor that you will use again is pointless. If you are running TPC benchmarks, then yes.

But if you are using MODE ANSI databases and try to re-open an already open cursor, you get an error. Besides, it is good practice to close what you open; this applies to forms, windows, cursors, reports (START), transactions, ...

4.1.8.6 Move Math from Query to Application

Better

SELECT item_num, quantity FROM items

LET quantity=quantity*10

Worse
SELECT item_num, quantity*10 FROM items

4.1.8.7 Avoid Scroll Cursors

Never use a scroll cursor if it is not really needed. They create temporary tables when you OPEN them.

4.1.8.8 Update Necessary Columns Only

Particularly avoid updating indexed columns, as this results in needless overhead on the engine.

Better
UPDATE orders SET(backlog, shipdate) = (p_orders.backlog, p_orders.shipdate)
Worse
UPDATE orders SET * = p_orders.*

The "worse" also runs into problems if the database structure changes. In many circumstances, it is better to write out the names of the columns instead... This is not entirely uncontroversial; it also takes more effort.

4.1.8.9 Organize Common Operations into Functions

Do not duplicate code several times throughout a program. Set it up as a function and call it with perhaps a flag if any slight variations are required. Same should apply to any standard pieces of code (like Contract pricing) which should live in a single module which can be called by any program.

4.1.8.10 Reduce Transaction Length

Try to keep the number of INSERT, DELETE, or UPDATE statements used within a BEGIN WORK/COMMIT block to a minimum. This will reduce lock usage, and the possibility of a long transaction (OnLine only).

4.1.8.11 SELECT needed columns only

Better
SELECT fname, lname, phone FROM customer WHERE...
Worse
SELECT * FROM customer WHERE...

4.1.9 How can I use #DEFINE-style constructs, like C?

There's nothing built into 4GL, but many people use the Unix "M4" command successfully. (Note: M4 has been ported to DOS as a part of the GNUish project too). You could also use "cpp".

Stuart Kemp (stuart@cs.jcu.edu.au):

To use the C preprocessor (cpp) in conjunction with GNU make you might use a suffix of ".cpp" on the files you edit, and then build a Makefile containing:

 
.SUFFIXES: .4gi .4go .4gl .cpp .frm .per 
 
.cpp.4gl: 
        @echo Make $@ from $< $(CPPDEFS) 
        @$(CPP) $(CPPDEFS) $< > $@ 
 
.per.frm: 
        @echo Make $@ from $< 
        @form4gl -s $< 
 
.4gl.4go: 
        fglpc $< 
 
 
Of course, the downside of this is that if you get an error-message when 
running your .4g[io] program, the line-number will be that in the .4gl file, 
not the .cpp file. 
 

4.1.10 Why are RUN's return codes screwy?

Because return codes have been multiplied by 256.

exit 1 - check for 256
exit 2 512 etc...
so:
RUN "someprogram" RETURNING l_exit
LET l_exit = l_exit / 256

Note that this apparently is not a problem on all hardware platforms :-(

rizzo@fourgee.demon.co.uk (Rus Ambler)

Note also that this bug has been fixed in 4.13/6.01 of 4GL

4.1.11 Are there any RDS compatability problems?

Dennis J. Pimple (dennisp@informix.com) informs:

Differences are minor. RDS initializes variables (to NULL for CHARs, 0 for numeric, and 12/31/1899 for DATEs), while in c compiled (and as good coding practice) variable initialization is done by the programmer. There are some bugs in RDS not manifested in c- ompiled and vice versa, but nothing too severe (the most frustrating one I can think of off the top of my head is that WORDWRAP in a report doesn't work as well in RDS as it does in compiled, meaning that you might pound at a problem in RDS that isn't any kind of problem in compiled).

To err on the side of caution, I'd suggest a final test in c-compiled before rolling code into production, but I wouldn't spend a major amount of time on it.

4.1.12 Can you link Compiled I4GL functions into a custom P-code runner?

johnl@informix.com (Jonathan Leffler) says:

Answer: No.

Discussion:

Can I compile an arbitrary I4GL source file into a C-code object file and link this into a custom P-code runner (fglgo or fgldb)?

Empirical evidence shows that it nearly works, but there are usually linking problems caused by missing references, such as _efadsply(), _fardsply(), _efprmpt(), _efdsply() _efemsg(), _efmsg(), and _finput(). Additionally, even if you don't run into these problems, it seems that embedded SQL's do not work correctly when they are inside I4GL functions which are bound into the runners.

Investigation of the source code shows that it is, in general, not advisable to try building I4GL C-code into the P-code runners.

You can sometimes get away with linking some, typically simple, I4GL C-code functions into the P-code interpreter. These will typically be utility functions which neither access the database nor interact with the user.

Note that it is easy to append the I4GL library functions (compiled with fglpc) to the interpretable (.4gi), so there is seldom a real need to embed I4GL code into the P-code runners.

4.1.13 How can I check if any rows exist, without OPEN-FETCH-CLOSE?

From: akent@cix.compulink.co.uk ("Andy Kent")

The Question:

Does anybody know a simple and efficient way of selecting any record in a non-singleton select statement without having to resort to 4GLxs open, fetch and close?

The easiest way, assuming the tuples returned will have the same values, is to use SELECT DISTINCT colnames, but this is very inefficient as it has to read all the rows then group them in a temp table.

If all the cols you are selecting are in an index, then something like SELECT MIN(col1),MIN(col2) would do the job. By reading the whole lot through an index then not only does it only need to touch one row, but it will do a key-only select (ie. it can get all it needs from the index page so it doesn't need to bother reading the data page). This is very efficient.

OPEN..FETCH..CLOSE is the best way.

4.1.14 How do you include Printer control characters in an Informix Report?

Some applications, like creating sexy output on a laser printer or printing barcodes, require the output of escape sequences. To achieve this you need to use the internal function "ASCII".

PRINT ASCII 027 will print an escape, so if you want to imbedd "ESC&(6" you would: PRINT ASCII 027, "&(6"

You have to play around with top and bottom margins, page length, etc, to make sure that pages break the way you want and other details.

dennisp@informix.com (Dennis Pimple)

For increased readability and portability you might want to consider sticking your escape sequences into character strings, and then printing those:

 
    DEFINE  l_bar     CHAR(1) 
 
    LET l_bar = ASCII(124) 
          ... 
    PRINT COLUMN  1, l_bar, 
          COLUMN  5, l_sometext, 
          COLUMN 80, l_bar 

4.1.15 How can I dynamically change Report OPTIONs at runtime?

There is not yet any Informix-supported way to dynamically size reports in Informix-4GL. However, there is an alternative solution which is, I believe, in the FTP archives at ftp.iiug.org. It is also reproduced in Appendix J.

4.1.16 My .err files are incomplete

John Regep (kmart!jregep@uunet.uu.net) and Uwe Kraul (uwe@orchide.bb.bawue.de) summarized the following list of reasons describing why .err files might only contain a portion of the original file

Cause: An omitted END IF or END CASE especially around large quantities of code and/or large amounts of nesting. The compiler can get confused and just stop!

Solution: Comment out large pieces of code until error stops, then put small amounts of code back until error re-appears (or Code better)

Cause: Mis-spelled tablename in the following statement DEFINE fieldX LIKE tablename.fieldname (or record name used instead of tablename)

Solution: Same as above.

Cause: Missing errmsg file from /usr/informix/msg

Solution: Put'em back.

Cause: Table(s) not converted correctly from previous version of product. error: Assertion failed: file fecat.ec when recompileing. The .err file will contain the source code up to a define record statement, the rest of the file will be truncated. It does not matter if the records are defined global or local. Looking at the tables with isql with the table info option results in an assertion failed error message. Bcheck returns no errors.

Solution: Workaround seems to be to unload the table run dbschema -t, drop it and then recreate and load it.

Cause: You've got some code that looks like this: LET vondats = dateterm(MDY(von_mon,von_day,von_year))

Solution: LET dat_4gl=MDY(....) LET von_dats= dateterm(dat_4gl)

4.1.17 Can I run a Stored Procedure from 4GL?

johnl@informix.com (Jonathan Leffler) says:

Yes, you can execute stored procedures from I4GL 4.10 (and earlier). You cannot simply write EXECUTE PROCEDURE; you have to prepare the statement:

PREPARE p_exec FROM "EXECUTE PROCEDURE procname(?,?,?)"

What you do next depends on whether the procedure returns any values or not. If it does not, then you can simply use:

EXECUTE p_exec USING value01, value02, value03

If it does return parameters, then you should declare a cursor for the statement:

DECLARE c_exec CURSOR FOR p_exec

OPEN c_exec USING value01, value02, value03

WHILE STATUS = 0
    FETCH c_exec INTO return01, return02, return03, return04
    IF STATUS != 0 THEN
        EXIT WHILE
    END IF
    ...
END WHILE

CLOSE c_exec


Obviously, you can either hardwire the parameters into the procedure
argument list or have an argument-less procedure, and in both cases, you
can avoid the USING clause, and you can use a FOREACH loop to fetch the
data.

If the procedure can raise exceptions, then you do not have to worry about them as return values, because the values you supply in the RAISE EXCEPTION clause will be placed in SQLCA.SQLCODE (and thence into STATUS) and SQLCA.SQLERRD[2] and SQLCA.SQLERRM. You should of course be using error -746 for returning your own error messages.

The 4.10 manuals state that you cannot prepare the EXECUTE statement. This is still correct, because EXCECUTE PROCEDURE is a different statement from the EXECUTE statement.

On 18th July 2001 lawry@nildram.co.uk (Doug Lawry) wrote:-

The following is supported in 4GL 7.3 and Four J's (and aubit4gl), and is rather easier:


    SQL
        EXECUTE PROCEDURE proc_name($parameter1, $parameter2, $parameter3)
    END SQL

4.1.18 What problems will I have upgrading from 4.x to 6/7.x?

Jonathan Leffler (johnl@informix.com):

Watch out for bug B31661:

When migrating from 4.1x to 6.0x, watch out for statements where you prepare an update or delete statement and reference a cursor with WHERE CURRENT OF cursorname:

DECLARE c_select CURSOR FOR
SELECT Column1, Column2 FROM SomeTable FOR UPDATE

LET upd_stmt = "UPDATE SomeTable SET (Column1, Column2) = (?, ?)"
"WHERE CURRENT OF c_select"
PREPARE p_update FROM upd_stmt

FOREACH c_select INTO variable1, variable2
-- modify the variables
EXECUTE p_update USING variable1, variable2
END FOREACH


This will fail with a cursor not found error, unless you compiled the module with the c4gl option "-globcurs". If you do that, you must make sure that all your cursor names and statement names are unique across all modules compiled with "-globcurs" in effect.

The approved way of fixing this problem is to build the upd_stmt string with the special function CURSOR_NAME:

LET upd_stmt = "UPDATE SomeTable SET (Column1, Column2) = (?, ?)"
"WHERE CURRENT OF ", CURSOR_NAME("c_select")


This will work correctly even when cursor names are being mangled. The CURSOR_NAME function was introduced in 4.13/6.01 (and is not available in 4.12/6.00 or earlier releases). The 4.1x version of CURSOR_NAME() does nothing; the 6.0x version mangles the name in the same way as the c_select cursor name is mangled -- look in the release notes for more information.

This is the fallout from bug number B31661, and the fix is not transparent to the user, and cannot be transparent to the user. The problem arises because cursor names are mangled in an attempt to retain the 4.1x behaviour of cursor names being private to a source file even though the underlying 6.00 ESQL/C only supports global cursor names.

Alan Goodman (alan@dssmktg.com), Paul Watson (ifpxw@ifl.co.uk), and me:

4.1.19 Tips on running 4GL via Cron

Jonathan Leffler (johnl@informix.com): The rules are very simple:

(1) The program should not rely on having a terminal connected.

(2) The environment needs to be set correctly.

Item (1) means, in general, that I4GL programs using INPUT or other screen mode operations (including DISPLAY AT) are unreliable, but plain DISPLAY statements and reports are fine. ISQL can be run in the batch mode to interpret SQL scripts, and to run ACE reports (or to compile reports or forms), but running forms or using menus or the schema editor, etc, is error-prone at best.

Item (2) means that cron should be told to run a script which sets the complete environment for the program. When cron runs a job, it has a completely minimal environment: typically, the environment variables HOME, LOGNAME, PATH are set to default values (PATH is probably just /usr/bin on Solaris 2.4), and that is about it. If you're lucky, you'll get TZ and HZ set too. So, depending on the version of Informix you are running, where you have Informix installed, and what your cron-run jobs are doing, you may need to set:

    INFORMIXDIR     -- probably
    PATH            -- definitely
    INFORMIXSERVER  -- 6.00 and up
    SQLEXEC         -- 5.0x and down, maybe
    SQLRM           -- 5.0x, maybe
    SQLRMDIR        -- 5.0x, maybe
    DBPATH          -- possibly
    DBDATE          -- if you aren't in the USA
    DBMONEY         -- if you aren't in the USA
    DBFORMAT        -- if you aren't in the USA

You may want to set many other environment variables. Some of these may be defaults for your application system; others will be less commonly used Informix variables.

4.1.20 Will Informix explode in the year 2000?

When we enter a date into a form with a 2 digit year (eg 1/1/96) Informix adds "1900" to it nicely for me. Will it add "2000" at the turn of the century?

The bad news: No, current versions will not. You'll have to change all your screens to accept 4 digit years and check to make sure sensible values have been entered. You might want to add a CHECK constraint to your date columns:

CHECK (YEAR(datecol) >= 1950)

The good news: If your code is based on a product using the (as yet unreleased) 7.20 ESQL/C, or some later version, will you get the more flexible behaviour given by the DBCENTURY environment variable, which is documented below. At the moment, there are no plans to back-port this to any of the version 4, 5 or 6 products.

The environment variable DBCENTURY should be set to one of:

DBCENTURY 1/1/90 1/1/02 1/1/97 2/16/96
C 1/1/1990 1/1/2002 1/1/1997 2/16/1996
P 1/1/1990 1/1/1902 1/1/1897 2/16/1896
F 1/1/2090 1/1/2002 1/1/1997 2/16/2096
R 1/1/1990 1/1/1902 1/1/1997 2/16/1996
Information from Johnathan Leffler and June Tong
nigel@ppsl.com (Edmund Nigel Gall) writes:-

Check out the following URL to get the official policy regarding DBCENTURY and its support in various versions:-

http://www.informix.com/informix/products/year2000.htm

On 8th May 1998 jleffler@visa.com (Leffler, Jonathan) wrote:-

The earliest product versions which understand DBCENTURY are:

On 21st Oct 1998 cmageanu@lhr-sys.DHL.COM (Cristian Mageanu) wrote:-

This is a famous one. Bug no 60150. The engine is not able to recognize the 29th of Feb., 2000 as a valid date. First versions that have this bug fixed are 7.23.UC9, 7.24.UC4 and 7.30.UC1.

4.1.21 How can I use GNU C (gcc) with 4GL?

If you're suffering from core-dumps, try editing $INFORMIXDIR/bin/c4gl and changing the line:

CC=${CC:-cc} to CC=${CC:-"gcc -fwritable-strings"}

4.1.22 Why does 4GL generate invalid dates when it performs arithmetic with months?

When 4GL adds months to a date, it sometimes ends up with a invalid date:

January 31st + 1 UNITS MONTH = February 31st

Doh! There is no February 31st! There are those who argue that 4GL should evaluate the above to February 28th (or 29th if it's a leap year), and others who feel it should return a date sometime in early March. The fact is that it doesn't make either of these decisions for you, and it never will. Write your own routine instead, and make it do whatever your business considers most appropriate.

jparker@boi.hp.com (Jack Parker) wrote:

Some of you will run into this today.....

	SELECT TODAY - 1 UNITS MONTH	fails - invalid date - 6-31-96 

The user in question was interested in the month portion of this so her query was:

	SELECT MONTH(TODAY) - 1 UNITS MONTH	which also failed.

I showed her:

	SELECT MONTH(TODAY - DAY(TODAY) UNITS DAY)  which works, except...

What she REALLY wanted:

	SELECT EXTEND(TODAY - DAY(TODAY) UNITS DAY), YEAR TO MONTH)

Which yields the year portion as well - sort of vital come January or when you have more than 1 years worth of data.

4.1.23 How do I disconnect from the engine in 4GL?

david@smooth1.co.uk (David Williams) writes:

I wrote a 4GL function to do this


#include <stdio.h>
#include <stdlib.h>

#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

fnc_sqlexit(n)
int n;
{
    int i,status;

    if (n != 0) 
        exit(1);
    
    sqlexit();     

    status=wait(&i);
    if ((status==-1) && (errno!=ECHILD))
      {
      fprintf(stderr,"\n\nStatus = %d\n\n",errno);
      sleep(4);
      }

    /* Return 0 args to Informix */
    return(0);
}

This has been tested under

The wait part is to handle the sqlexec becoming a zombie under SE 4.x on some platforms.

johnl@informix.com (Jonathan Leffler) writes:

I posted some ESQL/C code to allow I4GL program to use CONNECT and friends via a set of function calls. Check in the IIUG archives http://www.iiug.org for the source code.

4.1.24 How does free work?

johnl@informix.com (Jonathan Leffler) wrote:
I4GL 4.1x and ISQL 4.1x use 4.12 ESQL/C, so if you are using 4.1x I4GL, the
pre-5.0 rules apply (freeing a statement frees the cursor, and vice versa).

I4GL 6.0x and ISQL 6.0x use 6.00 ESQL/C, so if you are using 6.0x I4GL, the
post-5 rules apply (you free both the statement and the cursor).

NewEra 1.0x was based on 5.0x ESQL/C, so the post-5 rules apply to all
versions of NewEra.


In the 7.1 Syntax manual, it says (p1-276 and 1-277):

    Freeing a statement

    If you declared a cursor for a prepared statement, FREE
    statement-id releases only the resources in the application
    development tool; the cursor can still be used.  The resources in
    the database server are released only when you free the cursor.

    After freeing a statement, you cannot execute it or declare a
    cursor for it until you prepare it again.

    Freeing a cursor

    If you declared a cursor for a prepared statement, freeing the
    cursor releases only the resources in the database server.  To
    release the resources for the statement in the application
    development tool, use FREE statement-id.

    After a cursor is freed, it cannot be opened until it is declared
    again.  It is recommended that the cursor be explicitly closed
    before it is freed.

    David Williams says:

    So in pre-5.x you free the statement id OR the cursor NOT BOTH.

    In post 5.x you 

      CLOSE the CURSOR
      FREE the CURSOR    to free engine resources
      FREE the STATEMENT-ID to free 4GL resources
     

4.1.25 Removed

4.1.26 How do I find the Nth root of Number in 4GL?

On 18th Jun 1999 sblack@elsouth.com (Scott Black) wrote:-


{#######################################################################
#
   PROGRAM      n_root.4gl
   PURPOSE:     return the nth root of x  (can also use 1/n to get
powers of x)
   HISTORY      07/12/96 Scott Black Original

########################################################################
}

function n_root(x, n)

define x float,
       n float,
       root float,
       str char(150)

       let str = "select exp((logn(?)/?)) ans",
                 "  from systables where tabid = 1"

       prepare n_root from str
       declare root_cur cursor for n_root
       open root_cur using x, n
       fetch root_cur into root

   close root_cur
   free n_root

   return root
end function

4.1.27 What was the first version 6.01 release of 4GL?

On 18th Dec 1997 johnl@informix.com (Jonathan Leffler) wrote:-

The first official release was 6.01.UD1; the 6.01.UC1 release was supposed to be withheld because of a minor packaging problem, and was replaced by 6.01.UD1. However, it wouldn't be totally unheard of for someone to have 6.01.UC1.

4.1.28 How do I get the P-Code version of a .4go?

On 9th Oct 1998 jleffler@earthlink.net (Jonathan Leffler) wrote:-

Byte one (counting from one) should be hex 0x01. The next two bytes are the p-code version number in a short integer, MSB first.


4.1.29 How do I stop error logging after calling startlog()?

On 19th Feb 1998 yosemite@netcom.com (Alan Denney) wrote:-

No, there is no official, documented shut_off_logging_dammit() function.

However, in theory, one could call a programmer-defined C function that in turn called fgl_clog() (with no parameters) out of fgllib.

But that would be using undocumented internals. That would be dangerous. That would be, well, just WRONG. WRONG, I tell you! Bad programmer! B-A-D programmer!

4.1.30 How do I use shared libraries under HP-UX?

On 9th Mar 1998 john@rl.is (John H. Frantz) wrote:-

Well, Malcom and I decided to actually open some manuals and figure out how things work. We're both on HP/UX 10.x so our results were almost exactly the same and I think we were both surprised how easy it was.

To describe my final methods, I'll use a simple example with the following source code:

  modmain.4gl   A 4gl module containing a main function.
  mod1.4gl      A 4gl module containing sub functions.
  mod2.4gl      A 4gl module containing sub functions.
  modc.c        A c module containing sub functions.

1. Put everything except the modmain.4gl in a shared library (for big systems you might want a few).

  c4gl -c -shared mod1.4gl mod2.4gl modc.c
  ld -b -o libmine.sl mod1.o mod2.o modc.o

The c module could also be compiled with "cc -c +z modc.c". The result is a group of corresponding object files (.o) in PIC form (position independent code, required for use in shared libraries).

2. Compile the main function in non PIC form because it doesn't have to be PIC and it will be slightly smaller compiled normally.

  c4gl -c modmain.4gl

Other modules that for some reason you don't want in a shared library (?) should be included at this point.

3. Create the final program using shared libraries.

  c4gl -shared -o modmain.4ge modmain.o -L. -l mine

Note the "-L." means you don't have to copy or move your shared library elsewhere as the linker will also look for shared libraries in your current directory.

4. Run the program. Because of the way c4gl resolves shared libraries (the "+s" option), your shared libraries don't have to be in any particular place. The environment variable SHLIB_PATH can be used to point to your shared library location at run time.

  export SHLIB_PATH=/opt/mine
  modmain.4ge

This can be useful if you have a development environment on the same machine.

Note: Because PIC objects are considerably larger than normal objects in HP/UX, the size of a large compiled 4gl program will actually be larger when the -shared option is used by itself. This is contrary to the whole purpose of using the -shared option to reduce the code size.

Additional savings in code size can be achieved using the strip command or similar options with the linker.

4.1.31 How should I handle locking in 4GL programs?

On 4th Aug 1999 kagel@bloomberg.net (Art S. Kagel) wrote:-

You should never use SELECT .... FOR UPDATE for interactive programs simply because of the problem you are intimating about. Your user could tie up the row and go out to lunch or off to Tahiti on a honeymoon.

It is best to use a timestamp scheme, where every interactively updated table contains a timestamp column that is inserted by a default constraint and updated by an update trigger. Then you FETCH the row with no lock and let the user update the screen form. When the form is committed refetch the row, or at least the timestamp column, FOR UPDATE and if the timestamp has changed then another user has updated the row. Then you release the lock you are holding and you need to notify the user and do something intelligent (redisplay the new version of the row, show both versions for comparison by the user, follow a fixed merge algorithm to merge the separate changes, whatever). If the timestamp is not changed you then UPDATE...WHERE CURRENT OF cursor and commit instantly so you never hold a lock for more than a fraction of a second and every app that is lock sensitive can use SET LOCK MODE TO WAIT 5 to wait for such quick locks to clear.

4.1.32 How can I use Purify with 4GL programs?

On 4th Oct 1999 jleffler@earthlink.net (Jonathan Leffler) wrote:-

I never had any problem. Just set INFORMIXC="purify cc" and run c4gl just as before. It works a treat

4.1.33 How can I properly log errors with 4GL programs?

On 13th July 1999 jleffler@earthlink.net (Jonathan Leffler) wrote:-

STARTLOG() does not report any error when it fails to open the file. Therefore, ERRORLOG() can fail. I always used to get around this by also writing a 'started' message to the log with ERRORLOG() immediately after doing the STARTLOG(). This way, if there's a problem, it happens at program startup, not 95% of the way through the user's work.

However, one of the lesser known backwaters of 4.12 I4GL was the function pair fgl_startlog() and fgl_errorlog(). IIRC, the fgl_errorlog() function is substantially identical to the traditional ERRORLOG() function, but the fgl_startlog() function returns a value 0 on success or negative on failure. These were covered in the release notes at one point; whether the information subsequently got lost is debatable.

4.1.34 What should the function main in a 4gl program contain?

On 10th December 1998 jleffler@earthlink.net (Jonathan Leffler) wrote:-

I am probably being unduly pedantic if I point out that you only need the non-proceduralDATABASE statement at the top of the file if you define variables LIKE table elements, or records LIKE tables. In general, and IMNSHO, if you are playing with multi-database systems, then the MAIN should be in a source file which contains no LIKE variable, so there is no need for the non-procedural database, which in turn means there's no need for the to exist at runtime because MAIN will not connect to any database by default unless it is compiled with a non-procedural database statement. This also speeds up the program startup because it avoids connecting to two databases before it really gets going, and connecting to a database is a relatively slow operation.

In fact, with just a small modicum of care, you can write a generic MAIN program for your project and use it for all your programs:


MAIN
    DEFER INTERRUPT
    DEFER QUIT
    CALL select_database()
    CALL real_main()
END MAIN

There are numerous minor variations on this theme; the key point is that apart from the DEFER operations (which must appear in MAIN), you simply compile this 6-line file, and each actual program starts with a function real_main() which contains the real program.