PostgreSQLLa base de données la plus sophistiquée au monde.
Documentation PostgreSQL 15.10 » Interfaces client » ECPG -- SQL embarqué en C » Utiliser les Zones de Descripteur

36.7. Utiliser les Zones de Descripteur

Une zone de descripteur SQL (SQL Descriptor Area ou SQLDA) est une méthode plus sophistiquée pour traiter le résultat d'un ordre SELECT, FETCH ou DESCRIBE. Une zone de descripteur SQL regroupe les données d'un enregistrement avec ses métadonnées dans une seule structure. Ces métadonnées sont particulièrement utiles quand on exécute des ordres SQL dynamiques, où_la nature des colonnes résultat ne sont pas forcément connues à l'avance. PostgreSQL fournit deux façons d'utiliser des Zones de Descripteur: les Zones de Descripteur SQL nommée et les structures C SQLDA.

36.7.1. Zones de Descripteur SQL nommées

Une zone descripteur SQL nommé est composée d'un entête, qui contient des données concernant l'ensemble du descripteur, et une ou plusieurs zones de descriptions d'objets, qui en fait décrivent chaque colonne de l'enregistrement résultat.

Avant que vous puissiez utiliser une zone de descripteur SQL, vous devez en allouer une :

EXEC SQL ALLOCATE DESCRIPTOR identifiant;
    

L'identifiant sert de « nom de variable » de la zone de descripteur. Quand vous n'avez plus besoin du descripteur, vous devriez le désallouer:

EXEC SQL DEALLOCATE DESCRIPTOR identifiant;
    

Pour utiliser une zone de descripteur, spécifiez le comme cible de stockage dans une clause INTO, à la place d'une liste de variables hôtes :

EXEC SQL FETCH NEXT FROM mycursor INTO SQL DESCRIPTOR mydesc;
    

Si le jeu de données retourné est vide, la zone de descripteur contiendra tout de même les métadonnées de la requête, c'est à dire les noms des champs.

Pour les requêtes préparées mais pas encore exécutées, l'ordre DESCRIBE peut être utilisé pour récupérer les métadonnées du résultat :

EXEC SQL BEGIN DECLARE SECTION;
char *sql_stmt = "SELECT * FROM table1";
EXEC SQL END DECLARE SECTION;

EXEC SQL PREPARE stmt1 FROM :sql_stmt;
EXEC SQL DESCRIBE stmt1 INTO SQL DESCRIPTOR mydesc;
    

Avant PostgreSQL 9.0, le mot clé SQL était optionnel, par conséquent utiliser DESCRIPTOR et SQL DESCRIPTOR produisaent les mêmes zones de descripteur SQL. C'est maintenant obligatoire, et oublier le mot clé SQL produit des zones de descripteurs SQLDA, voyez Section 36.7.2.

Dans les ordres DESCRIBE et FETCH, les mots-clés INTO et USING peuvent être utilisés de façon similaire : ils produisent le jeu de données et les métadonnées de la zone de descripteur.

Maintenant, comment récupérer les données de la zone de descripteur ? Vous pouvez voir la zone de descripteur comme une structure avec des champs nommés. Pour récupérer la valeur d'un champ à partir de l'entête et le stocker dans une variable hôte, utilisez la commande suivante :

EXEC SQL GET DESCRIPTOR name :hostvar = field;
    

À l'heure actuelle, il n'y a qu'un seul champ d'entête défini: COUNT, qui dit combien il y a de zones de descripteurs d'objets (c'est à dire, combien de colonnes il y a dans le résultat). La variable hôte doit être de type integer. Pour récupérer un champ de la zone de description d'objet, utilisez la commande suivante :

EXEC SQL GET DESCRIPTOR name VALUE num :hostvar = field;
    

num peut être un integer literal, ou une variable hôte contenant un integer. Les champs possibles sont :

CARDINALITY (integer)

nombres d'enregistrements dans le résultat

DATA

objet de donnée proprement dit (par conséquent, le type de données de ce champ dépend de la requête)

DATETIME_INTERVAL_CODE (integer)

Quand TYPE est 9, DATETIME_INTERVAL_CODE aura une valeur de 1 pour DATE, 2 pour TIME, 3 pour TIMESTAMP, 4 pour TIME WITH TIME ZONE, or 5 pour TIMESTAMP WITH TIME ZONE.

DATETIME_INTERVAL_PRECISION (integer)

non implémenté

INDICATOR (integer)

l'indicateur (indique une valeur null ou une troncature de valeur)

KEY_MEMBER (integer)

non implémenté

LENGTH (integer)

longueur de la donnée en caractères

NAME (string)

nom de la colonne

NULLABLE (integer)

non implémenté

OCTET_LENGTH (integer)

longueur de la représentation caractère de la donnée en octets

PRECISION (integer)

précision (pour les types numeric)

RETURNED_LENGTH (integer)

longueur de la donnée en caractères

RETURNED_OCTET_LENGTH (integer)

longueur de la représentation caractère de la donnée en octets

SCALE (integer)

échelle (pour le type numeric)

TYPE (integer)

code numérique du type de données de la colonne

Dans les ordres EXECUTE, DECLARE and OPEN, l'effet des mots clés INTO and USING est différent. Une zone de descripteur peut aussi être construite manuellement pour fournir les paramètres d'entré pour une requête ou un curseur et USING SQL DESCRIPTOR name est la façon de passer les paramètres d'entrée à une requête paramétrisée. L'ordre pour construire une zone de descripteur SQL est ci-dessous:

EXEC SQL SET DESCRIPTOR name VALUE num field = :hostvar;
    

PostgreSQL supporte la récupération de plus d'un enregistrement dans un ordre FETCH et les variables hôtes dans ce cas doivent être des tableaux. Par exemple :

EXEC SQL BEGIN DECLARE SECTION;
int id[5];
EXEC SQL END DECLARE SECTION;

EXEC SQL FETCH 5 FROM mycursor INTO SQL DESCRIPTOR mydesc;

EXEC SQL GET DESCRIPTOR mydesc VALUE 1 :id = DATA;
    

36.7.2. Zones de Descripteurs SQLDA

Une zone de descripteur SQLDA est une structure C qui peut aussi être utilisé pour récupérer les résultats et les métadonnées d'une requête. Une structure stocke un enregistrement du jeu de résultat.

EXEC SQL include sqlda.h;
sqlda_t         *mysqlda;

EXEC SQL FETCH 3 FROM mycursor INTO DESCRIPTOR mysqlda;
    

Netez que le mot clé SQL est omis. Les paragraphes qui parlent des cas d'utilisation de INTO and USING dans Section 36.7.1 s'appliquent aussi ici, avec un point supplémentaire. Dans un ordre DESCRIBE le mot clé DESCRIPTOR peut être complètement omis si le mot clé INTO est utilisé :

EXEC SQL DESCRIBE prepared_statement INTO mysqlda;
    

Le déroulement général d'un programme qui utilise des SQLDA est:

  1. Préparer une requête, et déclarer un curseur pour l'utiliser.

  2. Déclarer une SQLDA pour les lignes de résultat.

  3. Déclarer une SQLDA pour les paramètres d'entrées, et les initialiser (allocation mémoire, positionnement des paramètres).

  4. Ouvrir un curseur avec la SQLDA d'entrée.

  5. Récupérer les enregistrements du curseur, et les stocker dans une SQLDA de sortie.

  6. Lire les valeurs de la SQLDA de sortie vers les variables hôtes (avec conversion si nécessaire).

  7. Fermer le curseur.

  8. Libérer la zone mémoire allouée pour la SQLDA d'entrée.

36.7.2.1. Structure de Données SQLDA

Les SQLDA utilisent 3 types de structures de données: sqlda_t, sqlvar_t, et struct sqlname.

Astuce

La structure de la SQLDA de PostgreSQL est similaire à celle de DB2 Universal Database d'IBM, des informations techniques sur la SQLDA de DB2 peuvent donc aider à mieux comprendre celle de PostgreSQL.

36.7.2.1.1. Structure sqlda_t

Le type de structure sqlda_t est le type de la SQLDA proprement dit. Il contient un enregistrement. Et deux ou plus sqlda_t peuvent être connectées par une liste chaînée par le pointeur du champ desc_next, représentant par conséquent une collection ordonnée d'enregistrements. Par conséquent, quand deux enregistrements ou plus sont récupérés, l'application peut les lire en suivant le pointeur desc_next dans chaque nœud sqlda_t.

La définition de sqlda_t est :

struct sqlda_struct
{
    char            sqldaid[8];
    long            sqldabc;
    short           sqln;
    short           sqld;
    struct sqlda_struct *desc_next;
    struct sqlvar_struct sqlvar[1];
};

typedef struct sqlda_struct sqlda_t;
      

La signification des champs est :

sqldaid

Elle contient la chaîne littérale "SQLDA ".

sqldabc

Il contient la taille de l'espace alloué en octets.

sqln

Il content le nombre de paramètres d'entrée pour une requête paramétrique, dans le cas où il est passé à un ordre OPEN, DECLARE ou EXECUTE utilisant le mot clé USING. Dans le cas où il sert de sortie à un ordre SELECT, EXECUTE ou FETCH statements, sa valeur est la même que celle du champ sqld.

sqld

Il contient le nombre de champs du résultat.

desc_next

Si la requête retourne plus d'un enregistrement, plusieurs structures SQLDA chaînées sont retournées, et desc_next contient un pointeur vers l'élément suivant (enregistrement) de la liste.

sqlvar

C'est le tableau des colonnes du résultat.

36.7.2.1.2. Structure de sqlvar_t

Le type structure sqlvar_t contient la valeur d'une colonne et les métadonnées telles que son type et sa longueur. La définition du type est :

struct sqlvar_struct
{
    short          sqltype;
    short          sqllen;
    char          *sqldata;
    short         *sqlind;
    struct sqlname sqlname;
};

typedef struct sqlvar_struct sqlvar_t;
      

La signification des champs est :

sqltype

Contient l'identifiant de type du champ. Pour les valeurs, voyez enum ECPGttype dans ecpgtype.h.

sqllen

Contient la longueur binaire du champ, par exemple 4 octets pour ECPGt_int.

sqldata

Pointe vers la donnée. Le format de la donnée est décrit dans Section 36.4.4.

sqlind

Pointe vers l'indicateur de nullité. 0 signifie non nul, -1 signifie nul. null.

sqlname

Le nom du champ.

36.7.2.1.3. Structure struct sqlname

Une structure struct sqlname contient un nom de colonne. Il est utilisé comme membre de la structure sqlvar_t. La définition de la structure est :

#define NAMEDATALEN 64

struct sqlname
{
        short           length;
        char            data[NAMEDATALEN];
};
      

La signification des champs est :

length

Contient la longueur du nom du champ.

data

Contient le nom du champ proprement dit.

36.7.2.2. Récupérer un jeu de données au moyen d'une SQLDA

Les étapes générales pour récupérer un jeu de données au moyen d'une SQLDA sont :

  1. Déclarer une structure sqlda_t pour recevoir le jeu de données.

  2. Exécuter des commandes FETCH/EXECUTE/DESCRIBE pour traiter une requête en spécifiant la SQLDA déclarée.

  3. Vérifier le nombre d'enregistrements dans le résultat en inspectant sqln, un membre de la structure sqlda_t.

  4. Récupérer les valeurs de chaque colonne des membres sqlvar[0], sqlvar[1], etc., de la structure sqlda_t.

  5. Aller à l'enregistrement suivant (sqlda_t structure) en suivant le pointeur desc_next, un membre de la structure sqlda_t.

  6. Répéter l'étape ci-dessus au besoin.

Voici un exemple de récupération d'un jeu de résultats au moyen d'une SQLDA.

Tout d'abord, déclarer une structure sqlda_t pour recevoir le jeu de résultats.

sqlda_t *sqlda1;
     

Puis, spécifier la SQLDA dans une commande. Voici un exemple avec une commande FETCH.

EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
     

Faire une boucle suivant la liste chaînée pour récupérer les enregistrements.

sqlda_t *cur_sqlda;

for (cur_sqlda = sqlda1;
     cur_sqlda != NULL;
     cur_sqlda = cur_sqlda->desc_next)
{
    ...
}
     

Dans la boucle, faire une autre boucle pour récupérer chaque colonne de données (sqlvar_t) de l'enregistrement.

for (i = 0; i < cur_sqlda->sqld; i++)
{
    sqlvar_t v = cur_sqlda->sqlvar[i];
    char *sqldata = v.sqldata;
    short sqllen  = v.sqllen;
    ...
}
     

Pour récupérer une valeur de colonne, vérifiez la valeur de sqltype. Puis, suivant le type de la colonne, basculez sur une façon appropriée de copier les données du champ sqlvar vers une variable hôte.

char var_buf[1024];

switch (v.sqltype)
{
    case ECPGt_char:
        memset(&var_buf, 0, sizeof(var_buf));
        memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf) - 1 : sqllen));
        break;

    case ECPGt_int: /* integer */
        memcpy(&intval, sqldata, sqllen);
        snprintf(var_buf, sizeof(var_buf), "%d", intval);
        break;

    ...
}

36.7.2.3. Passer des Paramètres de Requête en Utilisant une SQLDA

La méthode générale pour utiliser une SQLDA pour passer des paramètres d'entrée à une requête préparée sont :

  1. Créer une requête préparée (prepared statement)

  2. Déclarer une structure sqlda_t comme SQLDA d'entrée.

  3. Allouer une zone mémorie (comme structure sqlda_t) pour la SQLDA d'entrée.

  4. Positionner (copier) les valeurs d'entrée dans la mémoire allouée.

  5. Ouvrir un curseur en spécifiant la SQLDA d'entrée.

Voici un exemple.

D'abord, créer une requête préparée.

EXEC SQL BEGIN DECLARE SECTION;
char query[1024] = "SELECT d.oid, * FROM pg_database d, pg_stat_database s WHERE d.oid = s.datid AND (d.datname = ? OR d.oid = ?)";
EXEC SQL END DECLARE SECTION;

EXEC SQL PREPARE stmt1 FROM :query;
     

Puis, allouer de la mémoire pour une SQLDA, et positionner le nombre de paramètres d'entrée dans sqln, une variable membre de la structure<sqlda_t>. Quand deux paramètres d'entrée ou plus sont requis pour la requête préparée, l'application doit allouer de la mémoire supplémentaire qui est calculée par (nombre de paramètres -1) * sizeof<sqlvar_t>. Les exemples affichés ici allouent de l'espace mémoire pour deux paramètres d'entrée.

sqlda_t *sqlda2;

sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));

sqlda2->sqln = 2; /* nombre de variables d'entrée */
     

Après l'allocation mémoire, stocker les valeurs des paramètres dans le tableausqlvar[]. (C'est le même tableau que celui qui est utilisé quand la SQLDA reçoit un jeu de résultats.) Dans cet exemple, les paramètres d'entrée sont "postgres", de type chaîne, et 1, de type integer.

sqlda2->sqlvar[0].sqltype = ECPGt_char;
sqlda2->sqlvar[0].sqldata = "postgres";
sqlda2->sqlvar[0].sqllen  = 8;

int intval = 1;
sqlda2->sqlvar[1].sqltype = ECPGt_int;
sqlda2->sqlvar[1].sqldata = (char *) &intval;
sqlda2->sqlvar[1].sqllen  = sizeof(intval);
     

En ouvrant un curseur et en spécifiant la SQLDA qui a été positionné auparavant, les paramètres d'entrée sont passés à la requête préparée.

EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
     

Et pour finir, après avoir utilisé les SQLDAs d'entrée, la mémoire allouée doit être libérée explicitement, contrairement aux SQLDAs utilisé pour recevoir le résultat d'une requête.

free(sqlda2);
     

36.7.2.4. Une application de Démonstration Utilisant SQLDA

Voici un programme de démonstration, qui montre comment récupérer des statistiques d'accès des bases, spécifiées par les paramètres d'entrée, dans les catalogues systèmes.

Cette application joint deux tables systèmes, pg_database et pg_stat_database sur l'oid de la base, et récupère et affiche aussi les statistiques des bases qui sont spécifiées par deux paramètres d'entrées (une base postgres et un OID 1).

Tout d'abord, déclarer une SQLDA pour l'entrée et une SQLDA pour la sortie.

EXEC SQL include sqlda.h;

sqlda_t *sqlda1; /* un descripteur de sortie */
sqlda_t *sqlda2; /* un descripteur d'entrée  */
     

Puis, se connecter à la base, préparer une requête, et déclarer un curseur pour la requête préparée.

int
main(void)
{
    EXEC SQL BEGIN DECLARE SECTION;
    char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";
    EXEC SQL END DECLARE SECTION;

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

    EXEC SQL PREPARE stmt1 FROM :query;
    EXEC SQL DECLARE cur1 CURSOR FOR stmt1;
     

Puis, mettre des valeurs dans la SQLDA d'entrée pour les paramètres d'entrée. Allouer de la mémoire pour la SQL d'entrée, et positionner le nombre de paramètres d'entrée dans sqln. Stocker le type, la valeur et la longueur de la valeur dans sqltype, sqldata et sqllen dans la structure sqlvar.

    /* Créer une structure SQLDA pour les paramètres d'entrée. */
    sqlda2 = (sqlda_t *) malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
    memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
    sqlda2->sqln = 2; /* number of input variables */

    sqlda2->sqlvar[0].sqltype = ECPGt_char;
    sqlda2->sqlvar[0].sqldata = "postgres";
    sqlda2->sqlvar[0].sqllen  = 8;

    intval = 1;
    sqlda2->sqlvar[1].sqltype = ECPGt_int;
    sqlda2->sqlvar[1].sqldata = (char *)&intval;
    sqlda2->sqlvar[1].sqllen  = sizeof(intval);
     

Après avoir positionné la SQLDA d'entrée, ouvrir un curseur avec la SQLDA d'entrée.

    /* Ouvrir un curseur avec les paramètres d'entrée. */
    EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;
     

Récupérer les enregistrements dans la SQLDA de sortie à partir du curseur ouvert. (En général, il faut appeler FETCH de façon répétée dans la boucle, pour récupérer tous les enregistrements du jeu de données.)

    while (1)
    {
        sqlda_t *cur_sqlda;

        /* Assigner le descripteur au curseur  */
        EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;
     

Ensuite, récupérer les enregistrements du FETCH de la SQLDA, en suivant la liste chaînée de la structure sqlda_t.

    for (cur_sqlda = sqlda1 ;
         cur_sqlda != NULL ;
         cur_sqlda = cur_sqlda->desc_next)
    {
        ...
     

Lire chaque colonne dans le premier enregistrement. Le nombre de colonnes est stocké dans sqld, les données réelles de la première colonne sont stockées dans sqlvar[0], tous deux membres de la structuresqlda_t.

        /* Afficher toutes les colonnes d'un enregistrement. */
        for (i = 0; i < sqlda1->sqld; i++)
        {
            sqlvar_t v = sqlda1->sqlvar[i];
            char *sqldata = v.sqldata;
            short sqllen  = v.sqllen;

            strncpy(name_buf, v.sqlname.data, v.sqlname.length);
            name_buf[v.sqlname.length] = '\0';
     

Maintenant, la donnée de la colonne est stockée dans la variable v. Copier toutes les données dans les variables host, en inspectant v.sqltype pour connaître le type de la colonne.

            switch (v.sqltype) {
                int intval;
                double doubleval;
                unsigned long long int longlongval;

                case ECPGt_char:
                    memset(&var_buf, 0, sizeof(var_buf));
                    memcpy(&var_buf, sqldata, (sizeof(var_buf) <= sqllen ? sizeof(var_buf)-1 : sqllen));
                    break;

                case ECPGt_int: /* integer */
                    memcpy(&intval, sqldata, sqllen);
                    snprintf(var_buf, sizeof(var_buf), "%d", intval);
                    break;

                ...

                default:
                    ...
            }

            printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
        }

Fermer le curseur après avoir traité tous les enregistrements, et se déconnecter de la base de données.

    EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;
     

Le programme dans son entier est visible dans Exemple 36.1.

Exemple 36.1. Programme de Démonstration SQLDA

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

EXEC SQL include sqlda.h;

sqlda_t *sqlda1; /* descripteur pour la sortie */
sqlda_t *sqlda2; /* descripteur pour l'entrée */

EXEC SQL WHENEVER NOT FOUND DO BREAK;
EXEC SQL WHENEVER SQLERROR STOP;

int
main(void)
{
    EXEC SQL BEGIN DECLARE SECTION;
    char query[1024] = "SELECT d.oid,* FROM pg_database d, pg_stat_database s WHERE d.oid=s.datid AND ( d.datname=? OR d.oid=? )";

    int intval;
    unsigned long long int longlongval;
    EXEC SQL END DECLARE SECTION;

    EXEC SQL CONNECT TO uptimedb AS con1 USER uptime;
    EXEC SQL SELECT pg_catalog.set_config('search_path', '', false); EXEC SQL COMMIT;

    EXEC SQL PREPARE stmt1 FROM :query;
    EXEC SQL DECLARE cur1 CURSOR FOR stmt1;

    /* Créer une structure SQLDB pour let paramètres d'entrée */
    sqlda2 = (sqlda_t *)malloc(sizeof(sqlda_t) + sizeof(sqlvar_t));
    memset(sqlda2, 0, sizeof(sqlda_t) + sizeof(sqlvar_t));
    sqlda2->sqln = 2; /* a number of input variables */

    sqlda2->sqlvar[0].sqltype = ECPGt_char;
    sqlda2->sqlvar[0].sqldata = "postgres";
    sqlda2->sqlvar[0].sqllen  = 8;

    intval = 1;
    sqlda2->sqlvar[1].sqltype = ECPGt_int;
    sqlda2->sqlvar[1].sqldata = (char *) &intval;
    sqlda2->sqlvar[1].sqllen  = sizeof(intval);

    /* Ouvrir un curseur avec les paramètres d'entrée. */
    EXEC SQL OPEN cur1 USING DESCRIPTOR sqlda2;

    while (1)
    {
        sqlda_t *cur_sqlda;

        /* Assigner le descripteur au curseur  */
        EXEC SQL FETCH NEXT FROM cur1 INTO DESCRIPTOR sqlda1;

        for (cur_sqlda = sqlda1 ;
             cur_sqlda != NULL ;
             cur_sqlda = cur_sqlda->desc_next)
        {
            int i;
            char name_buf[1024];
            char var_buf[1024];

            /* Afficher toutes les colonnes d'un enregistrement. */
            for (i=0 ; i<cur_sqlda->sqld ; i++)
            {
                sqlvar_t v = cur_sqlda->sqlvar[i];
                char *sqldata = v.sqldata;
                short sqllen  = v.sqllen;

                strncpy(name_buf, v.sqlname.data, v.sqlname.length);
                name_buf[v.sqlname.length] = '\0';

                switch (v.sqltype)
                {
                    case ECPGt_char:
                        memset(&var_buf, 0, sizeof(var_buf));
                        memcpy(&var_buf, sqldata, (sizeof(var_buf)<=sqllen ? sizeof(var_buf)-1 : sqllen) );
                        break;

                    case ECPGt_int: /* integer */
                        memcpy(&intval, sqldata, sqllen);
                        snprintf(var_buf, sizeof(var_buf), "%d", intval);
                        break;

                    case ECPGt_long_long: /* bigint */
                        memcpy(&longlongval, sqldata, sqllen);
                        snprintf(var_buf, sizeof(var_buf), "%lld", longlongval);
                        break;

                    default:
                    {
                        int i;
                        memset(var_buf, 0, sizeof(var_buf));
                        for (i = 0; i < sqllen; i++)
                        {
                            char tmpbuf[16];
                            snprintf(tmpbuf, sizeof(tmpbuf), "%02x ", (unsigned char) sqldata[i]);
                            strncat(var_buf, tmpbuf, sizeof(var_buf));
                        }
                    }
                        break;
                }

                printf("%s = %s (type: %d)\n", name_buf, var_buf, v.sqltype);
            }

            printf("\n");
        }
    }

    EXEC SQL CLOSE cur1;
    EXEC SQL COMMIT;

    EXEC SQL DISCONNECT ALL;

    return 0;
}
     

L'exemple suivant devrait ressembler à quelque chose comme ce qui suit (des nombres seront différents).

oid = 1 (type: 1)
datname = template1 (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = t (type: 1)
datallowconn = t (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig =  (type: 1)
datacl = {=c/uptime,uptime=CTc/uptime} (type: 1)
datid = 1 (type: 1)
datname = template1 (type: 1)
numbackends = 0 (type: 5)
xact_commit = 113606 (type: 9)
xact_rollback = 0 (type: 9)
blks_read = 130 (type: 9)
blks_hit = 7341714 (type: 9)
tup_returned = 38262679 (type: 9)
tup_fetched = 1836281 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)

oid = 11511 (type: 1)
datname = postgres (type: 1)
datdba = 10 (type: 1)
encoding = 0 (type: 5)
datistemplate = f (type: 1)
datallowconn = t (type: 1)
datconnlimit = -1 (type: 5)
datfrozenxid = 379 (type: 1)
dattablespace = 1663 (type: 1)
datconfig =  (type: 1)
datacl =  (type: 1)
datid = 11511 (type: 1)
datname = postgres (type: 1)
numbackends = 0 (type: 5)
xact_commit = 221069 (type: 9)
xact_rollback = 18 (type: 9)
blks_read = 1176 (type: 9)
blks_hit = 13943750 (type: 9)
tup_returned = 77410091 (type: 9)
tup_fetched = 3253694 (type: 9)
tup_inserted = 0 (type: 9)
tup_updated = 0 (type: 9)
tup_deleted = 0 (type: 9)