Thursday, February 16, 2012

4GL WHENEVER ERROR

This article is written in English and Portuguese
Este artigo está escrito em Inglês e Português

English version:

I believe this is the first time I cover a 4GL topic here. But a recent customer situation motivated me to write this. Hopefully most of the readers will just say "Yeah... Everybody knows that...", but it's not the first time I see people making the confusion I'm going to describe, and in my opinion that happens because it's not intuitive. Although it's perfectly documented, I suppose many people just follow the intuitive approach and fall into the problem.

I'm talking about the WHENEVER statement. It is used to define the behavior of the program when(ever) a defined condition (ERROR, SQLERROR, WARNING, SQLWARNING or  NOTFOUND) happens. The behavior can be CONTINUE, STOP, GOTO or CALL function. Seems pretty simple and handy... so why am I writing this? The usual confusion relates to the scope of the WHENEVER statement. At first glance you could think this was a program instruction, and the effect or scope of it would be until the program flow reached another WHENEVER statement. And this is the confusion many people make.
In reality it's not a program instruction, but instead it's a compiler directive. As stated in the documentation the scope is local to the module where it appears. If the module contains only function definitions, then all this functions will behave accordingly to the conditions used in the WHENEVER statement. The program flow is irrelevant.
This is better shown with a practical example (line numbers added for clarity)
 1  DATABASE sysmaster
 2  MAIN
 3        DEFINE v INTEGER
 4        CALL func_1() RETURNING v
 5  END MAIN
 6
 7  FUNCTION func_0()
 8        WHENEVER ERROR CONTINUE 
 9  END FUNCTION
10
11  FUNCTION func_1()
12        SELECT no_column FROM no_table
13        RETURN 1,2
14  END FUNCTION

Ok, this is a very simple 4GL program that uses sysmaster database. It has two functions:
  1. func_0
    Is never called, but contains a WHENEVER ERROR CONTINUE which tells 4GL to continue execution when it encounters an error
  2. func_1
    This contains two errors. It tries to access a table that doesn't exist and returns two values (and the CALL on line 4 expect only one)
If we compile and execute it we get:
cheetah@pacman.onlinedomus.net:fnunes-> c4gl -o test.4ge test.4gl;./test.4ge
Program stopped at "test.4gl", line number 4.
FORMS statement error number -1320.
A function has not returned the correct number of values 
expected by the calling function.
cheetah@pacman.onlinedomus.net:fnunes->

What happened? The first error to expect would be the SQL error... But 4GL ignores it and raises an error on the CALL line. Why? Because from line 8 onward the code ignores errors. But the CALL on line 4 is not protected against error. If we comment the func_0 function, and repeat we get:
cheetah@pacman.onlinedomus.net:fnunes-> c4gl -o test.4ge test.4gl;./test.4ge
Program stopped at "test.4gl", line number 12.
SQL statement error number -206.
The specified table (no_table) is not in the database.
SYSTEM error number -111.
ISAM error:  no record found.
cheetah@pacman.onlinedomus.net:fnunes->
This is the expected behavior, but note that we didn't change the program flow.

So, to be clear, WHENEVER ERROR acts at compile time, and means that from it's occurrence downwards, all the code within the module will be protected until another WHENEVER statement is reached. And it affects only the current module. It has nothing to do with the program execution map.

Just one last reminder: Never use WHENEVER without introducing code to test for errors (for CONTINUE condition). Otherwise your program may do unexpected things


Versão Portuguesa

Julgo que esta é a primeira vez que escrevo aqui de 4GL. A motivação deriva mais uma vez de uma situação vivida num cliente. Com sorte, muitos dos leitores dirão apenas "Sim... toda a gente sabe isso...", mas já não é a primeira vez que encontro a confusão que vou relatar, e na minha opinião tal acontece porque é algo pouco intuitivo. Apesar de estar claramente documentado, suponho que muitas pessoas apenas seguem o instinto e daí cairem no problema.

Estou a falar da instrução WHENEVER. É usada para definir o comportamento do programa sempre que uma determinada condição (ERROR, SQLERROR, WARNING, SQLWARNING ou  NOTFOUND) acontece. O comportamento pode ser CONTINUE, STOP, GOTO ou CALL de uma função. Parece muito simples e prático... Sendo assim porque estou a escrever sobre isto? A confusão habitual relaciona-se com o raio de acção da instrução. À primeira vista parece uma normal instrução de programa, e a sua acção extender-se-ia até que o fluxo de execução do programa encontrasse outro WHENEVER. E isto parece ser assumido por muita gente.
Mas na realidade não é uma instrução de programa, mas sim uma directiva de compilação ou compilador. Como é referido na documentação a sua influência restringe-se ao módulo onde é usada. Se um módulo só contém definições de funções, então essas funções comportar-se-ão conforme a instrução ditar. O fluxo de execução do programa é irrelevante.
É melhor mostrar isto com um exemplo (números de linhas adicionados por clareza):
 1  DATABASE sysmaster
 2  MAIN
 3        DEFINE v INTEGER
 4        CALL func_1() RETURNING v
 5  END MAIN
 6
 7  FUNCTION func_0()
 8        WHENEVER ERROR CONTINUE 
 9  END FUNCTION
10
11  FUNCTION func_1()
12        SELECT uma_coluna FROM nao_existe
13        RETURN 1,2
14  END FUNCTION

Isto é um programa 4GL extremamente simples. Usa a base de dados sysmaster e contém duas funções:
  1. func_0
    Nunca é chamada, mas contém um WHENEVER ERROR CONTINUE que força o 4GL a continuar a execução caso encontre algum erro
  2. func_1
    Esta função contém dois erros. Tenta aceder a uma tabela que não existe e retorna dois valores (a chamada CALL na linha 4 apenas espera um)
Se compilarmos e executarmos obtemos isto:
cheetah@pacman.onlinedomus.net:fnunes-> c4gl -o teste.4ge teste.4gl;./teste.4ge
Program stopped at "teste.4gl", line number 4.
FORMS statement error number -1320.
A function has not returned the correct number of values 
expected by the calling function.
cheetah@pacman.onlinedomus.net:fnunes->

O que aconteceu? O primeiro erro que esperávamos era o erro de SQL... Mas foi ignorado e o erro do CALL foi despoletado. Porquê? Porque da linha 8 em diante o código ignora os erros. Mas o CALL da linha 4 não está protegido. Se comentarmos a função func_0 e repetirmos obtemos:
cheetah@pacman.onlinedomus.net:fnunes-> c4gl -o teste.4ge teste.4gl;./teste.4ge
Program stopped at "teste.4gl", line number 12.
SQL statement error number -206.
The specified table (nao_existe) is not in the database.
SYSTEM error number -111.
ISAM error:  no record found.
cheetah@pacman.onlinedomus.net:fnunes->
Isto é o esperado, mas repare-se que não mudámos o fluxo de execução do programa.

Portanto, clarificando, WHENEVER ERROR actua na fase de compilação, e significa que daí em diante, todo o código desse módulo, ficará protegido contra erros até ao aparecimento de outro WHENEVER. Não tem portanto nada que ver com o fluxo de execução do programa

Apenas uma nota final: Nunca utilize o WHENEVER (com a opção de CONTINUE) sem introduzir código que teste os erros. Caso contrário o seu programa pode fazer coisas inesperadas

1 comment:

Restaurant Software said...

Using WHENEVER is equivalent to including code after every SQL statement, and (optionally) after certain other 4GL statements to take the specified action if the exceptional condition is detected. Without WHENEVER, program execution immediately stops when a run-time error occurs, unless the database is ANSI-compliant.
If you use WHENEVER ERROR with any option but STOP or CONTINUE, 4GL tests for errors by polling the global variable status.