Documentation PostgreSQL 14.22 » Interfaces client » ECPG -- SQL embarqué en C » Utiliser des Variables Hôtes

Précédent | Niveau supérieur | Suivant
36.3. Exécuter des Commandes SQL | Sommaire | 36.5. SQL Dynamique

## 36.4. Utiliser des Variables Hôtes

Dans Section 36.3 vous avez vu comment exécuter des ordres SQL dans un programme SQL embarqué. Certains de ces ordres n'ont utilisé que des valeurs constantes et ne fournissaient pas de moyen pour insérer des valeurs fournies par l'utilisateur dans des ordres ou pour permettre au programme de traiter les valeurs retournées par la requête. Ces types d'ordres ne sont pas très utiles dans des applications réelles. Cette section explique en détail comment faire passer des données entre votre programme en C et les ordres SQL embarqués en utilisant un simple mécanisme appelé variables hôtes. Dans un programme SQL embarqué nous considérons que les ordres SQL sont des invités dans le code du programme C qui est le langage hôte. Par conséquent, les variables du programme C sont appelées variables hôtes.

Une autre façon d'échanger des valeurs entre les serveurs PostgreSQL et les applications ECPG est l'utilisation de descripteurs SQL, décrits dans Section 36.7.

### 36.4.1. Aperçu

Passer des données entre le programme en C et les ordres SQL est particulièrement simple en SQL embarqué. Plutôt que d'avoir un programme qui donne des données dans un ordre SQL, ce qui entraîne des complications variées, comme protéger correctement la valeur, vous pouvez simplement écrire le nom d'une variable C dans un ordre SQL, préfixée par un deux-points. Par exemple :

    EXEC SQL INSERT INTO unetable VALUES (:v1, 'foo', :v2);

Cet ordre fait référence à deux variables C appelées `v1` et `v2` et utilise aussi une chaîne SQL classique, pour montrer que vous n'êtes pas obligé de vous cantonner à un type de données ou à l'autre.

Cette façon d'insérer des variables C dans des ordres SQL fonctionne partout où une expression de valeur est attendue dans un ordre SQL.

### 36.4.2. Sections Declare

Pour passer des données du programme à la base, par exemple comme paramètres d'une requête, ou pour passer des données de la base vers le programme, les variables C qui sont prévues pour contenir ces données doivent être déclarées dans des sections spécialement identifiées, afin que le préprocesseur SQL embarqué puisse s'en rendre compte.

Cette section commence par :

    EXEC SQL BEGIN DECLARE SECTION;

et se termine par:

    EXEC SQL END DECLARE SECTION;

Entre ces lignes, il doit y avoir des déclarations de variables C normales, comme :

    int   x = 4;
    char  foo[16], bar[16];

Comme vous pouvez le voir, vous pouvez optionnellement assigner une valeur initiale à une variable. La portée de la variable est déterminée par l'endroit où se trouve la section de déclaration dans le programme. Vous pouvez aussi déclarer des variables avec la syntaxe suivante, qui crée une section declare implicite :

    EXEC SQL int i = 4;

Vous pouvez avoir autant de sections de déclaration que vous voulez dans un programme.

Ces déclarations sont aussi envoyées dans le fichier produit comme des variables C normales, il n'est donc pas nécessaire de les déclarer une seconde fois. Les variables qui n'ont pas besoin d'être utilisées dans des commandes SQL peuvent être déclarées normalement à l'extérieur de ces sections spéciales.

La définition d'une structure ou d'un union doit aussi être présente dans une section `DECLARE`. Sinon, le préprocesseur ne peut pas traiter ces types, puisuq'il n'en connait pas la définition.

### 36.4.3. Récupérer des Résultats de Requêtes

Maintenant, vous devriez être capable de passer des données générées par votre programme dans une commande SQL. Mais comment récupérer les résultats d'une requête ? À cet effet, le SQL embarqué fournit certaines variantes spéciales de commandes `SELECT` et `FETCH` habituelles. Ces commandes ont une clause spéciale `INTO` qui spécifie dans quelles variables hôtes les valeurs récupérées doivent être stockées. `SELECT` est utilisé pour une requête qui ne retourne qu'un seul enregistrement, et `FETCH` est utilisé pour une requête qui retourne plusieurs enregistrement, en utilisant un curseur.

Voici un exemple :

    /*
     * Avec cette table:
     * CREATE TABLE test1 (a int, b varchar(50));
     */

    EXEC SQL BEGIN DECLARE SECTION;
    int v1;
    VARCHAR v2;
    EXEC SQL END DECLARE SECTION;

     ...

    EXEC SQL SELECT a, b INTO :v1, :v2 FROM test;

La clause `INTO` apparaît entre la liste de sélection et la clause `FROM`. Le nombre d'éléments dans la liste SELECT et dans la liste après `INTO` (aussi appelée la liste cible) doivent être égaux.

Voici un exemple utilisant la commande `FETCH` :

    EXEC SQL BEGIN DECLARE SECTION;
    int v1;
    VARCHAR v2;
    EXEC SQL END DECLARE SECTION;

     ...

    EXEC SQL DECLARE truc CURSOR FOR SELECT a, b FROM test;

     ...

    do
    {
        ...
        EXEC SQL FETCH NEXT FROM truc INTO :v1, :v2;
        ...
    } while (...);

Ici, la clause `INTO` apparaît après toutes les clauses normales.

### 36.4.4. Correspondance de Type

Quand les applications ECPG échangent des valeurs entre le serveur PostgreSQL et l'application C, comme quand elles récupèrent des résultats de requête venant du serveur, ou qu'elles exécutent des ordres SQL avec des paramètres d'entrée, les valeurs doivent être converties entre les types de données PostgreSQL et les types du language hôte (ceux du langage C). Une des fonctionnalités les plus importantes d'ECPG est qu'il s'occupe de cela automatiquement dans la plupart des cas.

De ce point de vue, il y a deux sortes de types de données : des types de données PostgreSQL simples, comme des `integer` et `text`, qui peuvent être lus et écrits directement par l'application. Les autres types PostgreSQL, comme `timestamp` ou `numeric` ne peuvent être accédés qu'à travers des fonctions spéciales de librairie; voyez Section 36.4.4.2.

Tableau 36.1 montre quels types de données de PostgreSQL correspondent à quels types C. Quand vous voulez envoyer ou recevoir une valeur d'un type PostgreSQL donné, vous devriez déclarer une variable C du type C correspondant dans la section declare.

Tableau 36.1. Correspondance Entre les Types PostgreSQL et les Types de Variables C

type de données PostgreSQL | type de variable hôte
--- | ---
`smallint` | `short`
`integer` | `int`
`bigint` | `long long int`
`decimal` | `decimal`[a]
`numeric` | `numeric`[b]
`real` | `float`
`double precision` | `double`
`smallserial` | `short`
`serial` | `int`
`bigserial` | `long long int`
`oid` | `unsigned int`
`character(`n`)`, `varchar(`n`)`, `text` | `char[`n`+1]`, `VARCHAR[`n`+1]`
`name` | `char[NAMEDATALEN]`
`timestamp` | `timestamp`[c]
`interval` | `interval`[d]
`date` | `date`[e]
`boolean` | `bool`[f]
`bytea` | `char *`, `bytea[`n`]`
[a] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 36.4.4.2. [b] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 36.4.4.2. [c] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 36.4.4.2. [d] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 36.4.4.2. [e] Ce type ne peut être accédé qu'à travers des fonctions spéciales de librairie. Voyez Section 36.4.4.2. [f] déclaré dans `ecpglib.h` si non natif

#### 36.4.4.1. Manipuler des Chaînes de Caractères

Pour manipuler des types chaînes de caractères SQL, comme `varchar` et `text`, il y a deux façons de déclarer les variables hôtes.

Une façon est d'utiliser `char[]`, un tableau de `char`, qui est la façon la plus habituelle de gérer des données texte en C.

    EXEC SQL BEGIN DECLARE SECTION;
        char str[50];
    EXEC SQL END DECLARE SECTION;

Notez que vous devez gérer la longueur vous-même. Si vous utilisez cette variable he comme variable cible d'une requête qui retourne une chaîne de plus de 49 caractères, un débordement de tampon se produira.

L'autre façon est d'utiliser le type `VARCHAR`, qui est un type spécial fourni par ECPG. La définition d'un tableau de type `VARCHAR` est convertie dans un `struct` nommé pour chaque variable. Une déclaration comme :

    VARCHAR var[180];

est convertie en:

    struct varchar_var { int len; char arr[180]; } var;

Le membre `arr` contient la chaîne terminée par un octet à zéro. Par conséquent, la variable hôte doit être déclarée avec la longueur incluant le terminateur de chaîne. Le membre `len` stocke la longueur de la chaîne stockée dans `arr` sans l'octet zéro final. Quand une variable hôte est utilisé comme entrée pour une requête, si `strlen` et `len` sont différents, le plus petit est utilisé.

`VARCHAR` peut être écrit en majuscule ou en minuscule, mais pas dans un mélange des deux.

Les variables hôtes `char` et `VARCHAR` peuvent aussi contenir des valeurs d'autres types SQL, qui seront stockés dans leur forme chaîne.

#### 36.4.4.2. Accéder à des Types de Données Spéciaux

ECPG contient des types spéciaux qui vous aident interagir facilement avec des types de données spéciaux du serveur PostgreSQL. En particulier, sont supportés les types `numeric`, `decimal`, `date`, `timestamp`, et `interval`. Ces types de données ne peuvent pas être mis de façon utile en correspondance avec des types primitifs du langage hôtes (tels que `int`, `long long int`, ou `char[]`), parce qu'ils ont une structure interne complexe. Les applications manipulent ces types en déclarant des variables hôtes dans des types spéciaux et en y accédant avec des fonctions de la librairie pgtypes. La librairie pgtypes, décrite en détail dans Section 36.6 contient des fonctions de base pour traiter ces types, afin que vous n'ayez pas besoin d'envoyer une requête au serveur SQL juste pour additionner un interval à un timestamp par exemple.

Les sous-sections suivantes décrivent ces types de données spéciaux. Pour plus de détails à propos des fonctions de librairie pgtype, voyez Section 36.6.

##### 36.4.4.2.1. timestamp, date

Voici une méthode pour manipuler des variables `timestamp` dans l'application hôte ECPG.

Tout d'abord, le programme doit inclure le fichier d'en-tête pour le type `timestamp` :

    #include <pgtypes_timestamp.h>

Puis, déclarez une variable hôte comme type `timestamp` dans la section declare :

    EXEC SQL BEGIN DECLARE SECTION;
    timestamp ts;
    EXEC SQL END DECLARE SECTION;

Et après avoir lu une valeur dans la variable hôte, traitez la en utilisant les fonctions de la librairie pgtypes. Dans l'exemple qui suit, la valeur `timestamp` est convertie sous forme texte (ASCII) avec la fonction `PGTYPEStimestamp_to_asc()` :

    EXEC SQL SELECT now()::timestamp INTO :ts;

    printf("ts = %s\n", PGTYPEStimestamp_to_asc(ts));

Cet exemple affichere des résultats de ce type:

    ts = 2010-06-27 18:03:56.949343

Par ailleurs, le type DATE peut être manipulé de la même façon. Le programme doit inclure `pgtypes_date.h`, déclarer une variable hôte comme étant du type date et convertir une valeur DATE dans sa forme texte en utilisant la fonction `PGTYPESdate_to_asc()`. Pour plus de détails sur les fonctions de la librairie pgtypes, voyez Section 36.6.

##### 36.4.4.2.2. interval

La manipulation du type `interval` est aussi similaire aux types `timestamp` et `date`. Il est nécessaire, par contre, d'allouer de la mémoire pour une valeur de type `interval` de façon explicite. Ou dit autrement, l'espace mémoire pour la variable doit être allouée du tas, et non de la pile.

Voici un programme de démonstration :

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

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        interval *in;
    EXEC SQL END DECLARE SECTION;

        EXEC SQL CONNECT TO testdb;
        EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

        in = PGTYPESinterval_new();
        EXEC SQL SELECT '1 min'::interval INTO :in;
        printf("interval = %s\n", PGTYPESinterval_to_asc(in));
        PGTYPESinterval_free(in);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

##### 36.4.4.2.3. numeric, decimal

La manipulation des types `numeric` et `decimal` est similaire au type `interval`: elle requiert de définir d'un pointeur, d'allouer de la mémoire sur le tas, et d'accéder la variable au mouyen des fonctions de librairie pgtypes. Pour plus de détails sur les fonctions de la librairie pgtypes, voyez Section 36.6.

Aucune fonction n'est fournie spécifiquement pour le type `decimal`. Une application doit le convertir vers une variable `numeric` en utilisant une fonction de la librairie pgtypes pour pouvoir le traiter.

Voici un programme montrant la manipulation des variables de type `numeric` et `decimal`.

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

    EXEC SQL WHENEVER SQLERROR STOP;

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        numeric *num;
        numeric *num2;
        decimal *dec;
    EXEC SQL END DECLARE SECTION;

        EXEC SQL CONNECT TO testdb;
        EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

        num = PGTYPESnumeric_new();
        dec = PGTYPESdecimal_new();

        EXEC SQL SELECT 12.345::numeric(4,2), 23.456::decimal(4,2) INTO :num, :dec;

        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 0));
        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 1));
        printf("numeric = %s\n", PGTYPESnumeric_to_asc(num, 2));

        /* Convertir le decimal en numeric pour montrer une valeur décimale. */
        num2 = PGTYPESnumeric_new();
        PGTYPESnumeric_from_decimal(dec, num2);

        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 0));
        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 1));
        printf("decimal = %s\n", PGTYPESnumeric_to_asc(num2, 2));

        PGTYPESnumeric_free(num2);
        PGTYPESdecimal_free(dec);
        PGTYPESnumeric_free(num);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

##### 36.4.4.2.4. bytea

La gestion du type `bytea` est aussi similaire au type `VARCHAR`. La définition d'un tableau de type `bytea` est convertie en une structure nommée pour chaque variable. Une déclaration comme :

    bytea var[180];

est traduit en :

    struct bytea_var { int len; char arr[180]; } var;

Le membre `arr` accueille des données au format binaire. Il peut même gérer `'\0'` comme faisant partie des données, contrairement à `VARCHAR`. La donnée est converti de/vers le format hexadécimal et envoyé/reçu par ecpglib.

### Note

Une variable `bytea` peut être utilisé uniquement quand bytea_output est configuré à `hex`.

#### 36.4.4.3. Variables Hôtes avec des Types Non-Primitifs

Vous pouvez aussi utiliser des tableaux, typedefs, structs et pointeurs comme variables hôtes.

##### 36.4.4.3.1. Arrays

Il y a deux cas d'utilisations pour des tableaux comme variables hôtes. Le premier est une façon de stocker des chaînes de texte dans des `char[]` ou `VARCHAR[]`, comme expliqué Section 36.4.4.1. Le second cas d'utilisation est de récupérer plusieurs enregistrements d'une requête sans utiliser de curseur. Sans un tableau, pour traiter le résultat d'une requête de plusieurs lignes, il est nécessaire d'utiliser un curseur et la commande `FETCH`. Mais avec une variable hôte de type variable, plusieurs enregistrements peuvent être récupérés en une seule fois. La longueur du tableau doit être définie pour pouvoir recevoir tous les enregistrements d'un coup, sans quoi un buffer overflow se produira probablement.

Les exemples suivants parcourent la table système `pg_database` et montrent tous les OIDs et noms des bases de données disponibles :

    int
    main(void)
    {
    EXEC SQL BEGIN DECLARE SECTION;
        int dbid[8];
        char dbname[8][16];
        int i;
    EXEC SQL END DECLARE SECTION;

        memset(dbname, 0, sizeof(char)* 16 * 8);
        memset(dbid, 0, sizeof(int) * 8);

        EXEC SQL CONNECT TO testdb;
        EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

        /* Récupérer plusieurs enregistrements dans des tableaux d'un coup. */
        EXEC SQL SELECT oid,datname INTO :dbid, :dbname FROM pg_database;

        for (i = 0; i < 8; i++)
            printf("oid=%d, dbname=%s\n", dbid[i], dbname[i]);

        EXEC SQL COMMIT;
        EXEC SQL DISCONNECT ALL;
        return 0;
    }

Cet exemple affiche le résultat suivant. (Les valeurs exactes dépendent de votre environnement.)

    oid=1, dbname=template1
    oid=11510, dbname=template0
    oid=11511, dbname=postgres
    oid=313780, dbname=testdb
    oid=0, dbname=
    oid=0, dbname=
    oid=0, dbname=

##### 36.4.4.3.2. Structures

Une structure dont les noms des membres correspondent aux noms de colonnes du résultat d'une requête peut être utilisée pour récupérer plusieurs colonnes d'un coup. La structure permet de gérer plusieurs valeurs de colonnes dans une seule variable hôte.

L'exemple suivant récupère les OIDs, noms, et tailles des bases de données disponibles à partir de la table système `pg_database`, et en utilisant la fonction `pg_database_size()`. Dans cet exemple, une variable structure `dbinfo_t` avec des membres dont les noms correspondent à chaque colonnes du résultat du `SELECT` est utilisée pour récupérer une ligne de résultat sans avoir besoin de mettre plusieurs variables hôtes dans l'ordre `FETCH`.

    EXEC SQL BEGIN DECLARE SECTION;
        typedef struct
        {
           int oid;
           char datname[65];
           long long int size;
        } dbinfo_t;

        dbinfo_t dbval;
    EXEC SQL END DECLARE SECTION;

        memset(&dbval, 0, sizeof(dbinfo_t));

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
        EXEC SQL OPEN cur1;

        /* quand la fin du jeu de données est atteint, sortir de la boucle while */
        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            /* Récupérer plusieurs colonnes dans une structure. */
            EXEC SQL FETCH FROM cur1 INTO :dbval;

            /* Afficher les membres de la structure. */
            printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, dbval.size);
        }

        EXEC SQL CLOSE cur1;

Cet exemple montre le résultat suivant. (Les valeurs exactes dépendent du contexte.)

    oid=1, datname=template1, size=4324580
    oid=11510, datname=template0, size=4243460
    oid=11511, datname=postgres, size=4324580
    oid=313780, datname=testdb, size=8183012

Les variables hôtes structures « absorbent » autant de colonnes que la structure a de champs. Des colonnes additionnelles peuvent être assignées à d'autres variables hôtes. Par exemple, le programme ci-dessus pourrait être restructuré comme ceci, avec la variable `size` hors de la structure :

    EXEC SQL BEGIN DECLARE SECTION;
        typedef struct
        {
           int oid;
           char datname[65];
        } dbinfo_t;

        dbinfo_t dbval;
        long long int size;
    EXEC SQL END DECLARE SECTION;

        memset(&dbval, 0, sizeof(dbinfo_t));

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT oid, datname, pg_database_size(oid) AS size FROM pg_database;
        EXEC SQL OPEN cur1;

        /* quand la fin du jeu de données est atteint, sortir de la boucle while */
        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            /* Récupérer plusieurs colonnes dans une structure. */
            EXEC SQL FETCH FROM cur1 INTO :dbval, :size;

            /* Afficher les membres de la structure. */
            printf("oid=%d, datname=%s, size=%lld\n", dbval.oid, dbval.datname, size);
        }

        EXEC SQL CLOSE cur1;

##### 36.4.4.3.3. Typedefs

Utilisez le mot clé `typedef` pour faire correspondre de nouveaux types aux types existants.

    EXEC SQL BEGIN DECLARE SECTION;
        typedef char mychartype[40];
        typedef long serial_t;
    EXEC SQL END DECLARE SECTION;

Notez que vous pourriez aussi utiliser:

    EXEC SQL TYPE serial_t IS long;

Cette déclaration n'a pas besoin de faire partie d'une section declare.

##### 36.4.4.3.4. Pointeurs

Vous pouvez déclarer des pointeurs vers les types les plus communs. Notez toutefois que vous ne pouvez pas utiliser des pointeurs comme variables cibles de requêtes sans auto-allocation. Voyez Section 36.7 pour plus d'information sur l'auto-allocation.

    EXEC SQL BEGIN DECLARE SECTION;
        int   *intp;
        char **charp;
    EXEC SQL END DECLARE SECTION;

### 36.4.5. Manipuler des Types de Données SQL Non-Primitives

Cette section contient des informations sur comment manipuler des types non-scalaires et des types de données définies au niveau SQL par l'utilisateur dans des applications ECPG. Notez que c'est distinct de la manipulation des variables hôtes des types non-primitifs, décrits dans la section précédente.

#### 36.4.5.1. Tableaux

Les tableaux SQL multi-dimensionnels ne sont pas directement supportés dans ECPG. Les tableaux SQL à une dimension peuvent être placés dans des variables hôtes de type tableau C et vice-versa. Néanmoins, lors de la création d'une instruction, ecpg ne connaît pas le type des colonnes, donc il ne peut pas vérifier si un tableau C est à placer dans un tableau SQL correspondant. Lors du traitement de la sortie d'une requête SQL, ecpg a suffisamment d'informations et, de ce fait, vérifie si les deux sont des tableaux.

Si une requête accède aux éléments d'un tableau séparément, cela évite l'utilisation des tableaux dans ECPG. Dans ce cas, une variable hôte avec un type qui peut être mis en correspondance avec le type de l'élément devrait être utilisé. Par exemple, si le type d'une colonne est un tableau d'`integer`, une variable hôte de type `int` peut être utilisée. Par ailleurs, si le type de l'élément est `varchar`, ou `text`, une variable hôte de type `char[]` ou `VARCHAR[]` peut être utilisée.

Voici un exemple. Prenez la table suivante :

    CREATE TABLE t3 (
        ii integer[]
    );

    testdb=> SELECT * FROM t3;
         ii
    -------------
     {1,2,3,4,5}
    (1 row)

Le programme de démonstration suivant récupère le 4ème élément du tableau et le stocke dans une variable hôte de type `int` :

    EXEC SQL BEGIN DECLARE SECTION;
    int ii;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[4] FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :ii ;
        printf("ii=%d\n", ii);
    }

    EXEC SQL CLOSE cur1;

Cet exemple affiche le résultat suivant :

    ii=4

Pour mettre en correspondance de multiples éléments de tableaux avec les multiples éléments d'une variable hôte tableau, chaque élément du tableau doit être géré séparément, par exemple :

    EXEC SQL BEGIN DECLARE SECTION;
    int ii_a[8];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii[1], ii[2], ii[3], ii[4] FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        EXEC SQL FETCH FROM cur1 INTO :ii_a[0], :ii_a[1], :ii_a[2], :ii_a[3];
        ...
    }

Notez à nouveau que

    EXEC SQL BEGIN DECLARE SECTION;
    int ii_a[8];
    EXEC SQL END DECLARE SECTION;

    EXEC SQL DECLARE cur1 CURSOR FOR SELECT ii FROM t3;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* FAUX */
        EXEC SQL FETCH FROM cur1 INTO :ii_a;
        ...
    }

ne fonctionnerait pas correctement dans ce cas, parce que vous ne pouvez pas mettre en correspondance une colonne de type tableau et une variable hôte de type tableau directement.

Un autre contournement possible est de stocker les tableaux dans leur forme de représentation texte dans des variables hôtes de type `char[]` ou `VARCHAR[]`. Pour plus de détails sur cette représentation, voyez Section 8.15.2. Notez que cela implique que le tableau ne peut pas être accédé naturellement comme un tableau dans le programme hôte (sans traitement supplémentaire qui transforme la représentation texte).

#### 36.4.5.2. Types Composite

Les types composite ne sont pas directement supportés dans ECPG, mais un contournement simple est possible. Les contournements disponibles sont similaires à ceux décrits pour les tableaux ci-dessus: soit accéder à chaque attribut séparément, ou utiliser la représentation externe en mode chaîne de caractères.

Pour les exemples suivants, soient les types et tables suivants :

    CREATE TYPE comp_t AS (intval integer, textval varchar(32));
    CREATE TABLE t4 (compval comp_t);
    INSERT INTO t4 VALUES ( (256, 'PostgreSQL') );

La solution la plus évidente est d'accéder à chaque attribut séparément. Le programme suivant récupère les données de la table exemple en sélectionnant chaque attribut du type `comp_t` séparément :

    EXEC SQL BEGIN DECLARE SECTION;
    int intval;
    varchar textval[33];
    EXEC SQL END DECLARE SECTION;

    /* Mettre chaque élément de la colonne de type composite dans la liste SELECT. */
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Récupérer chaque élément du type de colonne composite dans des variables hôtes. */
        EXEC SQL FETCH FROM cur1 INTO :intval, :textval;

        printf("intval=%d, textval=%s\n", intval, textval.arr);
    }

    EXEC SQL CLOSE cur1;

Pour améliorer cet exemple, les variables hôtes qui vont stocker les valeurs dans la commande `FETCH` peuvent être rassemblées sous forme de structure, voyez Section 36.4.4.3.2. Pour passer à la structure, l'exemple peut-être modifié comme ci dessous. Les deux variables hôtes, `intval` et `textval`, deviennent membres de `comp_t`, et la structure est spécifiée dans la commande `FETCH`.

    EXEC SQL BEGIN DECLARE SECTION;
    typedef struct
    {
        int intval;
        varchar textval[33];
    } comp_t;

    comp_t compval;
    EXEC SQL END DECLARE SECTION;

    /* Mettre chaque élément de la colonne de type composite dans la liste SELECT. */
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).intval, (compval).textval FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Mettre toutes les valeurs de la liste SELECT dans une structure. */
        EXEC SQL FETCH FROM cur1 INTO :compval;

        printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
    }

    EXEC SQL CLOSE cur1;

Bien qu'une structure soit utilisée dans la commande `FETCH`, les noms d'attributs dans la clause `SELECT` sont spécifiés un par un. Cela peut être amélioré en utilisant un `*` pour demander tous les attributs de la valeur de type composite.

    ...
    EXEC SQL DECLARE cur1 CURSOR FOR SELECT (compval).* FROM t4;
    EXEC SQL OPEN cur1;

    EXEC SQL WHENEVER NOT FOUND DO BREAK;

    while (1)
    {
        /* Mettre toutes les valeurs de la liste SELECT dans une structure. */
        EXEC SQL FETCH FROM cur1 INTO :compval;

        printf("intval=%d, textval=%s\n", compval.intval, compval.textval.arr);
    }
    ...

De cette façon, les types composites peuvent être mis en correspondance avec des structures de façon quasi transparentes, alors qu'ECPG ne comprend pas lui-même le type composite.

Et pour finir, il est aussi possible de stocker les valeurs de type composite dans leur représentation externe de type chaîne dans des variables hôtes de type `char[]` ou `VARCHAR[]`. Mais de cette façon, il n'est pas facilement possible d'accéder aux champs de la valeur dans le programme hôte.

#### 36.4.5.3. Types de Base Définis par l'Utilisateur

Les nouveaux types de base définis par l'utilisateur ne sont pas directement supportés par ECPG. Vous pouvez utiliser les représentations externes de type chaîne et les variables hôtes de type `char[]` ou `VARCHAR[]`, et cette solution est en fait appropriée et suffisante pour de nombreux types.

Voici un exemple utilisant le type de données `complex` de l'exemple tiré de Section 38.13. La représentation externe sous forme de chaîne de ce type est `(%lf,%lf)`, qui est définie dans les fonctions `complex_in()` et `complex_out()`. L'exemple suivant insère les valeurs de type complexe `(1,1)` et `(3,3)` dans les colonnes `a` et `b`, et les sélectionne à partir de la table après cela.

    EXEC SQL BEGIN DECLARE SECTION;
        varchar a[64];
        varchar b[64];
    EXEC SQL END DECLARE SECTION;

        EXEC SQL INSERT INTO test_complex VALUES ('(1,1)', '(3,3)');

        EXEC SQL DECLARE cur1 CURSOR FOR SELECT a, b FROM test_complex;
        EXEC SQL OPEN cur1;

        EXEC SQL WHENEVER NOT FOUND DO BREAK;

        while (1)
        {
            EXEC SQL FETCH FROM cur1 INTO :a, :b;
            printf("a=%s, b=%s\n", a.arr, b.arr);
        }

        EXEC SQL CLOSE cur1;

Cet exemple affiche le résultat suivant :

    a=(1,1), b=(3,3)

Un autre contournement est d'éviter l'utilisation directe des types définis par l'utilisateur dans ECPG et à la place créer une fonction ou un cast qui convertit entre le type défini par l'utilisateur et un type primitif que ECPG peut traiter. Notez, toutefois, que les conversions de types, particulièrement les implicites, ne devraient être introduits dans le système de typage qu'avec la plus grande prudence.

Par exemple,

    CREATE FUNCTION create_complex(r double, i double) RETURNS complex
    LANGUAGE SQL
    IMMUTABLE
    AS $$ SELECT $1 * complex '(1,0')' + $2 * complex '(0,1)' $$;

Après cette définition, ce qui suit

    EXEC SQL BEGIN DECLARE SECTION;
    double a, b, c, d;
    EXEC SQL END DECLARE SECTION;

    a = 1;
    b = 2;
    c = 3;
    d = 4;

    EXEC SQL INSERT INTO test_complex VALUES (create_complex(:a, :b), create_complex(:c, :d));

a le même effet que

    EXEC SQL INSERT INTO test_complex VALUES ('(1,2)', '(3,4)');

### 36.4.6. Indicateurs

Les exemples précédents ne gèrent pas les valeurs nulles. En fait, les exemples de récupération de données remonteront une erreur si ils récupèrent une valeur nulle de la base. Pour être capable de passer des valeurs nulles à la base ou d'un récupérer, vous devez rajouter une seconde spécification de variable hôte à chaque variable hôte contenant des données. Cette seconde variable est appelée l'indicateur et contient un drapeau qui indique si le datum est null, dans quel cas la valeur de la vraie variable hôte est ignorée. Voici un exemple qui gère la récupération de valeurs nulles correctement :

    EXEC SQL BEGIN DECLARE SECTION;
    VARCHAR val;
    int val_ind;
    EXEC SQL END DECLARE SECTION:

     ...

    EXEC SQL SELECT b INTO :val :val_ind FROM test1;

La variable indicateur `val_ind` sera zéro si la valeur n'était pas nulle, et sera négative si la valeur était nulle. (Voir Section 36.16 pour activer un comportement spécifique à Oracle.)

L'indicateur a une autre fonction: si la valeur de l'indicateur est positive, cela signifie que la valeur n'est pas nulle, mais qu'elle a été tronquée quand elle a été stockée dans la variable hôte.

Si l'argument `-r no_indicator` est passée au préprocesseur `ecpg`, il fonction dans le mode « no-indicator ». En mode no-indicator, si aucune variable indicator n'est spécifiée, les valeurs nulles sont signalées (en entrée et en sortie) pour les types chaînes de caractère comme des chaînes vides et pour les types integer comme la plus petite valeur possible pour le type (par exemple, `INT_MIN` pour `int`).