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

Saturday, February 11, 2012

Informix on AIX patch levels

IBM has recently publish a document stating the OS levels required  and other known issues:

http://www-01.ibm.com/support/docview.wss?uid=swg21579767

If you're already running Informix on AIX, or are planning to, then you should took a quick look at it

Thursday, February 09, 2012

Sweet CRM / Doce CRM

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

English Version:

A recent press release by Oninit is being echoed across the Internet. Oninit have completed the port of SugarCRM, an open-source CRM system to Informix.
This gives SugarCRM users the opportunity to use Informix as the underlying database to their CRM system, effectively taking advantage of all the features we all know and love (performance, scalability, high availability features, complete platform options, simplicity etc.).

But there is even more to this.. Accordingly to SugarCRM site there were already a number of points connnecting SugarCRM and IBM (Cognos and SugarCRM working together, Lotus integration, IBM systems etc.).
It's also important to note that from a cost reduction point of view, the free Informix versions or the ones with lower costs can be an excellent companion for SugarCRM.
You can start small... Informix will grow as your business.

This is another great news about integration of Informix with many products (MediaWiki, XWiki, iBatis, Hibernate, Drupal, Alfresco and others).
Congratulations to Oninit for another great job (following several TimeSeries activities)

Versão Portuguesa:

Um comunicado de imprensa recente, pela Oninit está a ter eco na Internet. A Oninit completou a adaptação para que o CRM open-source SugarCRM passe a trabalhar também com Informix.
Isto permite aos utilizadores de SugarCRM usar o Informixx como base de dados de suporte do seu sistema de CRM, aproveitando assim as funcionalidades que conhecemos e apreciamos (rapidez, capacidade de escalar, alta-disponibilidade, disponibilidade de várias plataformas, simplicidade etc.).


Mas há ainda mais sobre isto... Segundo o site do SugarCRM já existiam um número de pontos de contacto entr o SugarCRM e a IBM (ligação entre SugarCRM e Cognos, integração com Lotus. sistemas IBM etc.).
Há ainda que referir que numa óptica de poupança de custos, as versões gratuitas ou de menor custo do Informix podem ser uma excelente companhia para o SugarCRM.
Pode começar pequeno... O Informix acompanhará o crescimento do seu negócio.


Isto é mais uma excelente notícia sobre a integração de Informix com muitos outros produtos (MediaWiki, XWiki, iBatis, Hibernate, Drupal, Alfresco e outros).
Parabéns à Oninit pela excelente iniciativa (depois de várias atividades relacionadas com Informix Timeseries)

Sunday, February 05, 2012

Procedures / Procedimentos Owner vs Restricted

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

English version:

Introduction

This article focus on a little known aspect of stored procedures or functions. That probably explains why it was the less voted in a recent poll I've conducted. Nonetheless it's (from my point of view) a very interesting topic. During this article I'll be referring to procedures, but I could use the term functions.
If we take a look at the sysprocedures table we'll see a field called mode. This field is just one character and the values it can contain are:

  • D or d
    DBA
  • O or o
    Owner
  • P or p
    Protected
  • R or r
    Restricted
  • T or t
    Trigger
I'm not interested in all of these, but the lower case letters mean "protected" (created by the system), D is for DBA procedures. P is an old nomenclature for protected procedures. T is used for procedures defined as Trigger procedures. And then we have O and R. O for owner mode and R for restricted mode. What is the difference between them? Assume you're using informix user and you run:
CREATE PROCEDURE test()
END PROCEDURE
You'll have an OWNER mode procedure, owned by informix user. But if instead you run:
CREATE PROCEDURE myuser.test()
END PROCEDURE
You'll have a RESTRICTED mode procedure owned by myuser.
You need to have DBA privilege to create a procedure on behalfwith another user name.

Why RESTRICTED?

The reasons why the restricted mode procedures/functions were created are based on security. Let's imagine the following scenario:
  1. You have two databases called db1 and db2
  2. You have a user myuser with connect privileges on db1 and db2 and another user mydba with DBA privileges on db1
  3. User myuser needs to be connected to db1 and run a distributed query to db2
  4. The db2's DBA grants the required privileges on db2 to user myuser
Now, without the RESTRICTED mode procedures, mydba could create a local db1 procedure on behalf of myuser, and with that it could remotely access the data on db2. Note that the db2 DBA did not intend to give the privileges to anyone else beside myuser. So a local DBA could use it's privileges to abuse some of the remote privileges granted to some of the local users.
This is why the RESTRICTED mode was created. Every time we create a procedure on behalf of another user, it will be created as a RESTRICTED mode procedure. And as such any remote operation will be done using the currently logged user and not with the identity of the procedure owner (as it happens with OWNER mode procedures).

Other implications

So, the reasons for the creation of this new mode are explained and are good reasons. But there can be another implication. Note that I'll be referencing a product issue, but it's highly probable that you'd never notice it. But the fix for that bug introduced new limits and a new error so it can be interesting to dig a bit deeper on this.
Whenever we make a remote connection inside a statement we need to open a new database. And we need to keep a record of the current opened ones. The structure of the opened databases used to be an array of "only" 8 positions. And in certain conditions we could wrap around it without raising an error. And this could lead to a nasty situation where the "current" database was not the one it should be. I noticed this on a customer environment when we started to get error -674 (procedure not found) on a procedure called from a trigger. Why is this related to the restricted vs owner mode procedures? Because with the mixed use of restricted and owner mode procedures we raise the possibility of having the same database opened with different users (the owners and our current user).
Please don't be scared with this problem. The situation I got involved around 60 objects (tables and procedures) linked together by a complex sequence of triggers that called procedures, that made INSERTs/UPDATEs/DELETEs which in turn called other procedures etc..
This sequence was started by a simple INSERT. And it involved 5 databases. The array I mentioned earlier had 8 positions.
Since then, we fixed several things and now (11.50.xC9 and 11.70.xC3):
  1. The array was increased to 32 positions
  2. If we still achieve that limit a proper error will be raised (-26600)
  3. The documentation was improved (it didn't mention any limit and it still mentions 8, but it should be fixed soon)
Versão Portuguesa:

Introdução  

Este artigo foca um aspecto pouco conhecido das stored procedures (ou funções). O facto de ser desconhecido deve ajudar a explicar porque foi o menos votado para artigos num inquérito que realizei há pouco tempo. Apesar disso, é um assunto interessante (do meu ponto de vista). Durante este artigo irei referir na maior parte das vezes "procedimentos". Mas podemos assumir "funções".
Se dermos uma vista de olhos à tabela sysprocedures podemos reparar que contém uma coluna com o nome mode. É apenas um caracter e os valores que pode conter são:
  • D or d
    DBA
  • O or o
    Owner
  • P or p
    Protected
  • R or r
    Restricted
  • T or t
    Trigger
Não estou interessado nestes todos, mas para melhor entendimento, as letras minúsculas significam que o prodedimento (ou função) é "protegido" (criado pelo sistema). D é para procedimentos DBA. P é uma nomenclatura antiga para procedimentos protegidos. T é usado para procedimentos definidos como Trigger procedures. E depois temos os O e R. O para modo owner e R para modo restricted. Qual é a diferença entre ambos? Assuma que estamos a usar o utilizador informix e corremos:
CREATE PROCEDURE teste()
END PROCEDURE
Ficaremos com um procedimento em modo OWNER, cujo dono é o informix. Mas se em vez disso fizermos:
CREATE PROCEDURE myuser.teste()
END PROCEDURE
Ficaremos com um procedimento em modo RESTRICTED cujo dono é o myuser.
É necessário ter privilégios de DBA para criar procedimentos em nome de outro utilizador.

Porquê RESTRICTED?

As razões que levaram à criação do modo RESTRICTED para funções e procedimentos prendem-se com segurança. Vamos imaginar o seguinte cenário:
  1. Temos duas bases de dados chamadas bd1 e bd2
  2. Temos um utilizador myuser com privilégios de CONNECT em bd1 e bd2 e outro utilizador mydba com privilégios de DBA na bd1
  3. O utilizador myuser necessita de, estando conectado à bd1, correr uma query distribuída à bd2
  4. O DBA da bd2 faz o GRANT dos privilégios necessários na bd2 ao utilizador myuser
Ora, sem o modo RESTRICTED dos procedimentos, o utilizador mydba poderia criar um procedimento na bd1, em nome do utilizador myuser e nesse procedimento poderia aceder à bd2 usando a identidade do myuser (que tem privilégios na bd2). Note-se que o DBA da bd2 não tencionava dar os privilégios a mais ninguém que não o myuser. Portanto um DBA local poderia usar os seus privilégios para usufruir de privilégios remotos dados a utilizadores da sua base de dados.
Esta foi a razão que levou à criação deste novo modo. Em termos práticos, um procedimento criado como RESTRICTED executa todas as operações remotas com a identidade do utilizador que a está a executar e não com a identidade do utilizador que está definido como dono (que pode ser diferente de quem a criou).

Outras implicações

Portanto, as razões para a introdução deste novo modo estão apresentadas e são boas razões. Mas podem existir outras implicações. De seguida irei referir um bug do produto, mas é altamente improvável que venha a encontrá-lo. Mas a correcção introduziu algumas alterações que são dignas de nota e que valerão a pena gastar algum tempo com elas.
Cada vez que fazemos uma conexão remota, dentro de uma instrução SQL, temos de abrir a base de dados remota. E necessitamos de manter um registo das bases de dados abertas em cada momento. A estrutura que mantém essa informação era um array de "apenas" 8 posições. E em determinadas situações poderíamos "dar a volta" sem despoletar um erro apropriado. E isto poderia dar origem a uma situação onde a base de dados "actual" não era a que deveria ser (devido à forma como eram abertas e fechadas as ligações durante a execução de uma instrução SQL). Deparei-me com isto num ambiente de um cliente onde começamos a obter o erro -674 (procedure not found) num procedimento despoletado por um trigger. Como é que isto se relaciona com o tema deste artigo? Porque o uso misto de procedimentos em modo RESTRICTED e OWNER potencia um maior número de bases de dados abertas em simultâneo (cada conexão tem um utilizador específico associado que conforme o modo pode ser o dono dos procedimentos ou o utilizador da sessão).
Não fique assustado com este problema. Para melhor enquadrar, na situação que encontrei existiam cerca de 60 objectos (tabelas e procedimentos) ligados por uma complexa teia de triggers e procedimentos (triggers que chamavam procedimentos que fazia INSERTs, UPDATEs e DELETEs, que por sua vez faziam disparar outros triggers e assim sucessivamente).
A sequência era despoletada por um simples INSERT e envolvia 5 bases de dados distintas. O array mencionado anteriormente tinha apenas 8 posições.
Isto levou a várias correcções e agora (11.50.xC9 e 11.70.xC3):
  1. O array foi incrementado para 32 posições
  2. Se alguma vez atingirmos este limite (espero sinceramente que não) um erro apropriado será retornado (-26600)
  3. A documentação foi melhorada (não mencionada qualquer limite, sendo que de momento ainda refere 8... Deve ser corrigido brevemente)